]> git.saurik.com Git - wxWidgets.git/blob - src/mac/morefile/Search.cpp
ignore these
[wxWidgets.git] / src / mac / morefile / Search.cpp
1 /*
2 ** Apple Macintosh Developer Technical Support
3 **
4 ** IndexedSearch and the PBCatSearch compatibility function.
5 **
6 ** by Jim Luther, Apple Developer Technical Support Emeritus
7 **
8 ** File: Search.c
9 **
10 ** Copyright © 1992-1998 Apple Computer, Inc.
11 ** All rights reserved.
12 **
13 ** You may incorporate this sample code into your applications without
14 ** restriction, though the sample code has been provided "AS IS" and the
15 ** responsibility for its operation is 100% yours. However, what you are
16 ** not permitted to do is to redistribute the source as "DSC Sample Code"
17 ** after having made changes. If you're going to re-distribute the source,
18 ** we require that you make it clear in the source that the code was
19 ** descended from Apple Sample Code, but that you've made changes.
20 */
21
22 #include <Types.h>
23 #include <Gestalt.h>
24 #include <Timer.h>
25 #include <Errors.h>
26 #include <Memory.h>
27 #include <Files.h>
28 #include <TextUtils.h>
29
30 #define __COMPILINGMOREFILES
31
32 #include "MoreFile.h"
33 #include "MoreExtr.h"
34 #include "Search.h"
35
36 /*****************************************************************************/
37
38 enum
39 {
40 /* Number of LevelRecs to add each time the searchStack is grown */
41 /* 20 levels is probably more than reasonable for most volumes. */
42 /* If more are needed, they are allocated 20 levels at a time. */
43 kAdditionalLevelRecs = 20
44 };
45
46 /*****************************************************************************/
47
48 /*
49 ** LevelRecs are used to store the directory ID and index whenever
50 ** IndexedSearch needs to either scan a sub-directory, or return control
51 ** to the caller because the call has timed out or the number of
52 ** matches requested has been found. LevelRecs are stored in an array
53 ** used as a stack.
54 */
55 struct LevelRec
56 {
57 long dirModDate; /* for detecting most (but not all) catalog changes */
58 long dirID;
59 short index;
60 };
61 typedef struct LevelRec LevelRec;
62 typedef LevelRec *LevelRecPtr, **LevelRecHandle;
63
64
65 /*
66 ** SearchPositionRec is my version of a CatPositionRec. It holds the
67 ** information I need to resuming searching.
68 */
69 #if PRAGMA_ALIGN_SUPPORTED
70 #pragma options align=mac68k
71 #endif
72 struct SearchPositionRec
73 {
74 long initialize; /* Goofy checksum of volume information used to make */
75 /* sure we're resuming a search on the same volume. */
76 unsigned short stackDepth; /* Current depth on searchStack. */
77 short priv[11]; /* For future use... */
78 };
79 #if PRAGMA_ALIGN_SUPPORTED
80 #pragma options align=reset
81 #endif
82 typedef struct SearchPositionRec SearchPositionRec;
83 typedef SearchPositionRec *SearchPositionRecPtr;
84
85
86 /*
87 ** ExtendedTMTask is a TMTask record extended to hold the timer flag.
88 */
89 #if PRAGMA_ALIGN_SUPPORTED
90 #pragma options align=mac68k
91 #endif
92 struct ExtendedTMTask
93 {
94 TMTask theTask;
95 Boolean stopSearch; /* the Time Mgr task will set stopSearch to */
96 /* true when the timer expires */
97 };
98 #if PRAGMA_ALIGN_SUPPORTED
99 #pragma options align=reset
100 #endif
101 typedef struct ExtendedTMTask ExtendedTMTask;
102 typedef ExtendedTMTask *ExtendedTMTaskPtr;
103
104 /*****************************************************************************/
105
106 static OSErr CheckVol(ConstStr255Param pathname,
107 short vRefNum,
108 short *realVRefNum,
109 long *volID);
110
111 static OSErr CheckStack(unsigned short stackDepth,
112 LevelRecHandle searchStack,
113 Size *searchStackSize);
114
115 static OSErr VerifyUserPB(CSParamPtr userPB,
116 Boolean *includeFiles,
117 Boolean *includeDirs,
118 Boolean *includeNames);
119
120 static Boolean IsSubString(ConstStr255Param aStringPtr,
121 ConstStr255Param subStringPtr);
122
123 static Boolean CompareMasked(const long *data1,
124 const long *data2,
125 const long *mask,
126 short longsToCompare);
127
128 static void CheckForMatches(CInfoPBPtr cPB,
129 CSParamPtr userPB,
130 const Str63 matchName,
131 Boolean includeFiles,
132 Boolean includeDirs);
133
134 #if __WANTPASCALELIMINATION
135 #undef pascal
136 #endif
137
138 #if GENERATINGCFM
139
140 static pascal void TimeOutTask(TMTaskPtr tmTaskPtr);
141
142 #else
143
144 static pascal TMTaskPtr GetTMTaskPtr(void);
145
146 static void TimeOutTask(void);
147
148 #endif
149
150 #if __WANTPASCALELIMINATION
151 #define pascal
152 #endif
153
154 static long GetDirModDate(short vRefNum,
155 long dirID);
156
157 /*****************************************************************************/
158
159 /*
160 ** CheckVol gets the volume's real vRefNum and builds a volID. The volID
161 ** is used to help insure that calls to resume searching with IndexedSearch
162 ** are to the same volume as the last call to IndexedSearch.
163 */
164 static OSErr CheckVol(ConstStr255Param pathname,
165 short vRefNum,
166 short *realVRefNum,
167 long *volID)
168 {
169 HParamBlockRec pb;
170 OSErr error;
171
172 error = GetVolumeInfoNoName(pathname, vRefNum, &pb);
173 if ( error == noErr )
174 {
175 /* Return the real vRefNum */
176 *realVRefNum = pb.volumeParam.ioVRefNum;
177
178 /* Add together a bunch of things that aren't supposed to change on */
179 /* a mounted volume that's being searched and that should come up with */
180 /* a fairly unique number */
181 *volID = pb.volumeParam.ioVCrDate +
182 pb.volumeParam.ioVRefNum +
183 pb.volumeParam.ioVNmAlBlks +
184 pb.volumeParam.ioVAlBlkSiz +
185 pb.volumeParam.ioVFSID;
186 }
187 return ( error );
188 }
189
190 /*****************************************************************************/
191
192 /*
193 ** CheckStack checks the size of the search stack (array) to see if there's
194 ** room to push another LevelRec. If not, CheckStack grows the stack by
195 ** another kAdditionalLevelRecs elements.
196 */
197 static OSErr CheckStack(unsigned short stackDepth,
198 LevelRecHandle searchStack,
199 Size *searchStackSize)
200 {
201 OSErr result;
202
203 if ( (*searchStackSize / sizeof(LevelRec)) == (stackDepth + 1) )
204 {
205 /* Time to grow stack */
206 SetHandleSize((Handle)searchStack, *searchStackSize + (kAdditionalLevelRecs * sizeof(LevelRec)));
207 result = MemError(); /* should be noErr */
208 *searchStackSize = InlineGetHandleSize((Handle)searchStack);
209 }
210 else
211 {
212 result = noErr;
213 }
214
215 return ( result );
216 }
217
218 /*****************************************************************************/
219
220 /*
221 ** VerifyUserPB makes sure the parameter block passed to IndexedSearch has
222 ** valid parameters. By making this check once, we don't have to worry about
223 ** things like NULL pointers, strings being too long, etc.
224 ** VerifyUserPB also determines if the search includes files and/or
225 ** directories, and determines if a full or partial name search was requested.
226 */
227 static OSErr VerifyUserPB(CSParamPtr userPB,
228 Boolean *includeFiles,
229 Boolean *includeDirs,
230 Boolean *includeNames)
231 {
232 CInfoPBPtr searchInfo1;
233 CInfoPBPtr searchInfo2;
234
235 searchInfo1 = userPB->ioSearchInfo1;
236 searchInfo2 = userPB->ioSearchInfo2;
237
238 /* ioMatchPtr cannot be NULL */
239 if ( userPB->ioMatchPtr == NULL )
240 {
241 goto ParamErrExit;
242 }
243
244 /* ioSearchInfo1 cannot be NULL */
245 if ( searchInfo1 == NULL )
246 {
247 goto ParamErrExit;
248 }
249
250 /* If any bits except partialName, fullName, or negate are set, then */
251 /* ioSearchInfo2 cannot be NULL because information in ioSearchInfo2 is required */
252 if ( ((userPB->ioSearchBits & ~(fsSBPartialName | fsSBFullName | fsSBNegate)) != 0) &&
253 ( searchInfo2 == NULL ))
254 {
255 goto ParamErrExit;
256 }
257
258 *includeFiles = false;
259 *includeDirs = false;
260 *includeNames = false;
261
262 if ( (userPB->ioSearchBits & (fsSBPartialName | fsSBFullName)) != 0 )
263 {
264 /* If any kind of name matching is requested, then ioNamePtr in */
265 /* ioSearchInfo1 cannot be NULL or a zero-length string */
266 if ( (searchInfo1->hFileInfo.ioNamePtr == NULL) ||
267 (searchInfo1->hFileInfo.ioNamePtr[0] == 0) ||
268 (searchInfo1->hFileInfo.ioNamePtr[0] > (sizeof(Str63) - 1)) )
269 {
270 goto ParamErrExit;
271 }
272
273 *includeNames = true;
274 }
275
276 if ( (userPB->ioSearchBits & fsSBFlAttrib) != 0 )
277 {
278 /* The only attributes you can search on are the directory flag */
279 /* and the locked flag. */
280 if ( (searchInfo2->hFileInfo.ioFlAttrib & ~(ioDirMask | 0x01)) != 0 )
281 {
282 goto ParamErrExit;
283 }
284
285 /* interested in the directory bit? */
286 if ( (searchInfo2->hFileInfo.ioFlAttrib & ioDirMask) != 0 )
287 {
288 /* yes, so do they want just directories or just files? */
289 if ( (searchInfo1->hFileInfo.ioFlAttrib & ioDirMask) != 0 )
290 {
291 *includeDirs = true;
292 }
293 else
294 {
295 *includeFiles = true;
296 }
297 }
298 else
299 {
300 /* no interest in directory bit - get both files and directories */
301 *includeDirs = true;
302 *includeFiles = true;
303 }
304 }
305 else
306 {
307 /* no attribute checking - get both files and directories */
308 *includeDirs = true;
309 *includeFiles = true;
310 }
311
312 /* If directories are included in the search, */
313 /* then the locked attribute cannot be requested. */
314 if ( *includeDirs &&
315 ((userPB->ioSearchBits & fsSBFlAttrib) != 0) &&
316 ((searchInfo2->hFileInfo.ioFlAttrib & 0x01) != 0) )
317 {
318 goto ParamErrExit;
319 }
320
321 /* If files are included in the search, then there cannot be */
322 /* a search on the number of files. */
323 if ( *includeFiles &&
324 ((userPB->ioSearchBits & fsSBDrNmFls) != 0) )
325 {
326 goto ParamErrExit;
327 }
328
329 /* If directories are included in the search, then there cannot */
330 /* be a search on file lengths. */
331 if ( *includeDirs &&
332 ((userPB->ioSearchBits & (fsSBFlLgLen | fsSBFlPyLen | fsSBFlRLgLen | fsSBFlRPyLen)) != 0) )
333 {
334 goto ParamErrExit;
335 }
336
337 return ( noErr );
338
339 ParamErrExit:
340 return ( paramErr );
341 }
342
343 /*****************************************************************************/
344
345 /*
346 ** IsSubString checks to see if a string is a substring of another string.
347 ** Both input strings have already been converted to all uppercase using
348 ** UprString (the same non-international call the File Manager uses).
349 */
350 static Boolean IsSubString(ConstStr255Param aStringPtr,
351 ConstStr255Param subStringPtr)
352 {
353 short strLength; /* length of string */
354 short subStrLength; /* length of subString */
355 Boolean found; /* result of test */
356 short index; /* current index into string */
357
358 found = false;
359 strLength = aStringPtr[0];
360 subStrLength = subStringPtr[0];
361
362 if ( subStrLength <= strLength)
363 {
364 register short count; /* search counter */
365 register short strIndex; /* running index into string */
366 register short subStrIndex; /* running index into subString */
367
368 /* start looking at first character */
369 index = 1;
370
371 /* continue looking until remaining string is shorter than substring */
372 count = strLength - subStrLength + 1;
373
374 do
375 {
376 strIndex = index; /* start string index at index */
377 subStrIndex = 1; /* start subString index at 1 */
378
379 while ( !found && (aStringPtr[strIndex] == subStringPtr[subStrIndex]) )
380 {
381 if ( subStrIndex == subStrLength )
382 {
383 /* all characters in subString were found */
384 found = true;
385 }
386 else
387 {
388 /* check next character of substring against next character of string */
389 ++subStrIndex;
390 ++strIndex;
391 }
392 }
393
394 if ( !found )
395 {
396 /* start substring search again at next string character */
397 ++index;
398 --count;
399 }
400 } while ( count != 0 && (!found) );
401 }
402
403 return ( found );
404 }
405
406 /*****************************************************************************/
407
408 /*
409 ** CompareMasked does a bitwise comparison with mask on 1 or more longs.
410 ** data1 and data2 are first exclusive-ORed together resulting with bits set
411 ** where they are different. That value is then ANDed with the mask resulting
412 ** with bits set if the test fails. true is returned if the tests pass.
413 */
414 static Boolean CompareMasked(const long *data1,
415 const long *data2,
416 const long *mask,
417 short longsToCompare)
418 {
419 Boolean result = true;
420
421 while ( (longsToCompare != 0) && (result == true) )
422 {
423 /* (*data1 ^ *data2) = bits that are different, so... */
424 /* ((*data1 ^ *data2) & *mask) = bits that are different that we're interested in */
425
426 if ( ((*data1 ^ *data2) & *mask) != 0 )
427 result = false;
428
429 ++data1;
430 ++data2;
431 ++mask;
432 --longsToCompare;
433 }
434
435 return ( result );
436 }
437
438 /*****************************************************************************/
439
440 /*
441 ** Check for matches compares the search criteria in userPB to the file
442 ** system object in cPB. If there's a match, then the information in cPB is
443 ** is added to the match array and the actual match count is incremented.
444 */
445 static void CheckForMatches(CInfoPBPtr cPB,
446 CSParamPtr userPB,
447 const Str63 matchName,
448 Boolean includeFiles,
449 Boolean includeDirs)
450 {
451 long searchBits;
452 CInfoPBPtr searchInfo1;
453 CInfoPBPtr searchInfo2;
454 Str63 itemName; /* copy of object's name for partial name matching */
455 Boolean foundMatch;
456
457 foundMatch = false; /* default to no match */
458
459 searchBits = userPB->ioSearchBits;
460 searchInfo1 = userPB->ioSearchInfo1;
461 searchInfo2 = userPB->ioSearchInfo2;
462
463 /* Into the if statements that go on forever... */
464
465 if ( (cPB->hFileInfo.ioFlAttrib & ioDirMask) == 0 )
466 {
467 if (!includeFiles)
468 {
469 goto Failed;
470 }
471 }
472 else
473 {
474 if (!includeDirs)
475 {
476 goto Failed;
477 }
478 }
479
480 if ( (searchBits & fsSBPartialName) != 0 )
481 {
482 if ( (cPB->hFileInfo.ioNamePtr[0] > 0) &&
483 (cPB->hFileInfo.ioNamePtr[0] <= (sizeof(Str63) - 1)) )
484 {
485 /* Make uppercase copy of object name */
486 BlockMoveData(cPB->hFileInfo.ioNamePtr,
487 itemName,
488 cPB->hFileInfo.ioNamePtr[0] + 1);
489 /* Use the same non-international call the File Manager uses */
490 UpperString(itemName, true);
491 }
492 else
493 {
494 goto Failed;
495 }
496
497 {
498 if ( !IsSubString(itemName, matchName) )
499 {
500 goto Failed;
501 }
502 else if ( searchBits == fsSBPartialName )
503 {
504 /* optimize for name matching only since it is most common way to search */
505 goto Hit;
506 }
507 }
508 }
509
510 if ( (searchBits & fsSBFullName) != 0 )
511 {
512 /* Use the same non-international call the File Manager uses */
513 if ( !EqualString(cPB->hFileInfo.ioNamePtr, matchName, false, true) )
514 {
515 goto Failed;
516 }
517 else if ( searchBits == fsSBFullName )
518 {
519 /* optimize for name matching only since it is most common way to search */
520 goto Hit;
521 }
522 }
523
524 if ( (searchBits & fsSBFlParID) != 0 )
525 {
526 if ( ((unsigned long)(cPB->hFileInfo.ioFlParID) < (unsigned long)(searchInfo1->hFileInfo.ioFlParID)) ||
527 ((unsigned long)(cPB->hFileInfo.ioFlParID) > (unsigned long)(searchInfo2->hFileInfo.ioFlParID)) )
528 {
529 goto Failed;
530 }
531 }
532
533 if ( (searchBits & fsSBFlAttrib) != 0 )
534 {
535 if ( ((cPB->hFileInfo.ioFlAttrib ^ searchInfo1->hFileInfo.ioFlAttrib) &
536 searchInfo2->hFileInfo.ioFlAttrib) != 0 )
537 {
538 goto Failed;
539 }
540 }
541
542 if ( (searchBits & fsSBDrNmFls) != 0 )
543 {
544 if ( ((unsigned long)(cPB->dirInfo.ioDrNmFls) < (unsigned long)(searchInfo1->dirInfo.ioDrNmFls)) ||
545 ((unsigned long)(cPB->dirInfo.ioDrNmFls) > (unsigned long)(searchInfo2->dirInfo.ioDrNmFls)) )
546 {
547 goto Failed;
548 }
549 }
550
551 if ( (searchBits & fsSBFlFndrInfo) != 0 ) /* fsSBFlFndrInfo is same as fsSBDrUsrWds */
552 {
553 if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlFndrInfo),
554 (long *)&(searchInfo1->hFileInfo.ioFlFndrInfo),
555 (long *)&(searchInfo2->hFileInfo.ioFlFndrInfo),
556 sizeof(FInfo) / sizeof(long)) )
557 {
558 goto Failed;
559 }
560 }
561
562 if ( (searchBits & fsSBFlXFndrInfo) != 0 ) /* fsSBFlXFndrInfo is same as fsSBDrFndrInfo */
563 {
564 if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlXFndrInfo),
565 (long *)&(searchInfo1->hFileInfo.ioFlXFndrInfo),
566 (long *)&(searchInfo2->hFileInfo.ioFlXFndrInfo),
567 sizeof(FXInfo) / sizeof(long)) )
568 {
569 goto Failed;
570 }
571 }
572
573 if ( (searchBits & fsSBFlLgLen) != 0 )
574 {
575 if ( ((unsigned long)(cPB->hFileInfo.ioFlLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlLgLen)) ||
576 ((unsigned long)(cPB->hFileInfo.ioFlLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlLgLen)) )
577 {
578 goto Failed;
579 }
580 }
581
582 if ( (searchBits & fsSBFlPyLen) != 0 )
583 {
584 if ( ((unsigned long)(cPB->hFileInfo.ioFlPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlPyLen)) ||
585 ((unsigned long)(cPB->hFileInfo.ioFlPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlPyLen)) )
586 {
587 goto Failed;
588 }
589 }
590
591 if ( (searchBits & fsSBFlRLgLen) != 0 )
592 {
593 if ( ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRLgLen)) ||
594 ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRLgLen)) )
595 {
596 goto Failed;
597 }
598 }
599
600 if ( (searchBits & fsSBFlRPyLen) != 0 )
601 {
602 if ( ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRPyLen)) ||
603 ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRPyLen)) )
604 {
605 goto Failed;
606 }
607 }
608
609 if ( (searchBits & fsSBFlCrDat) != 0 ) /* fsSBFlCrDat is same as fsSBDrCrDat */
610 {
611 if ( ((unsigned long)(cPB->hFileInfo.ioFlCrDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlCrDat)) ||
612 ((unsigned long)(cPB->hFileInfo.ioFlCrDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlCrDat)) )
613 {
614 goto Failed;
615 }
616 }
617
618 if ( (searchBits & fsSBFlMdDat) != 0 ) /* fsSBFlMdDat is same as fsSBDrMdDat */
619 {
620 if ( ((unsigned long)(cPB->hFileInfo.ioFlMdDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlMdDat)) ||
621 ((unsigned long)(cPB->hFileInfo.ioFlMdDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlMdDat)) )
622 {
623 goto Failed;
624 }
625 }
626
627 if ( (searchBits & fsSBFlBkDat) != 0 ) /* fsSBFlBkDat is same as fsSBDrBkDat */
628 {
629 if ( ((unsigned long)(cPB->hFileInfo.ioFlBkDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlBkDat)) ||
630 ((unsigned long)(cPB->hFileInfo.ioFlBkDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlBkDat)) )
631 {
632 goto Failed;
633 }
634 }
635
636 /* Hey, we passed all of the tests! */
637
638 Hit:
639 foundMatch = true;
640
641 /* foundMatch is false if code jumps to Failed */
642 Failed:
643 /* Do we reverse our findings? */
644 if ( (searchBits & fsSBNegate) != 0 )
645 {
646 foundMatch = !foundMatch; /* matches are not, not matches are */
647 }
648
649 if ( foundMatch )
650 {
651
652 /* Move the match into the match buffer */
653 userPB->ioMatchPtr[userPB->ioActMatchCount].vRefNum = cPB->hFileInfo.ioVRefNum;
654 userPB->ioMatchPtr[userPB->ioActMatchCount].parID = cPB->hFileInfo.ioFlParID;
655 if ( cPB->hFileInfo.ioNamePtr[0] > 63 )
656 {
657 cPB->hFileInfo.ioNamePtr[0] = 63;
658 }
659 BlockMoveData(cPB->hFileInfo.ioNamePtr,
660 userPB->ioMatchPtr[userPB->ioActMatchCount].name,
661 cPB->hFileInfo.ioNamePtr[0] + 1);
662
663 /* increment the actual count */
664 ++(userPB->ioActMatchCount);
665 }
666 }
667
668 /*****************************************************************************/
669
670 /*
671 ** TimeOutTask is executed when the timer goes off. It simply sets the
672 ** stopSearch field to true. After each object is found and possibly added
673 ** to the matches buffer, stopSearch is checked to see if the search should
674 ** continue.
675 */
676
677 #if __WANTPASCALELIMINATION
678 #undef pascal
679 #endif
680
681 #if GENERATINGCFM
682
683 static pascal void TimeOutTask(TMTaskPtr tmTaskPtr)
684 {
685 ((ExtendedTMTaskPtr)tmTaskPtr)->stopSearch = true;
686 }
687
688 #else
689
690 static pascal TMTaskPtr GetTMTaskPtr(void)
691 ONEWORDINLINE(0x2e89); /* MOVE.L A1,(SP) */
692
693 static void TimeOutTask(void)
694 {
695 ((ExtendedTMTaskPtr)GetTMTaskPtr())->stopSearch = true;
696 }
697
698 #endif
699
700 #if __WANTPASCALELIMINATION
701 #define pascal
702 #endif
703
704 /*****************************************************************************/
705
706 /*
707 ** GetDirModDate returns the modification date of a directory. If there is
708 ** an error getting the modification date, -1 is returned to indicate
709 ** something went wrong.
710 */
711 static long GetDirModDate(short vRefNum,
712 long dirID)
713 {
714 CInfoPBRec pb;
715 Str31 tempName;
716 long modDate;
717
718 /* Protection against File Sharing problem */
719 tempName[0] = 0;
720 pb.dirInfo.ioNamePtr = tempName;
721 pb.dirInfo.ioVRefNum = vRefNum;
722 pb.dirInfo.ioDrDirID = dirID;
723 pb.dirInfo.ioFDirIndex = -1; /* use ioDrDirID */
724
725 if ( PBGetCatInfoSync(&pb) == noErr )
726 {
727 modDate = pb.dirInfo.ioDrMdDat;
728 }
729 else
730 {
731 modDate = -1;
732 }
733
734 return ( modDate );
735 }
736
737 /*****************************************************************************/
738
739 pascal OSErr IndexedSearch(CSParamPtr pb,
740 long dirID)
741 {
742 static LevelRecHandle searchStack = NULL; /* static handle to LevelRec stack */
743 static Size searchStackSize = 0; /* size of static handle */
744 SearchPositionRecPtr catPosition;
745 long modDate;
746 short index;
747 ExtendedTMTask timerTask;
748 OSErr result;
749 short realVRefNum;
750 Str63 itemName;
751 CInfoPBRec cPB;
752 long tempLong;
753 Boolean includeFiles;
754 Boolean includeDirs;
755 Boolean includeNames;
756 Str63 upperName;
757
758 timerTask.stopSearch = false; /* don't stop yet! */
759
760 /* If request has a timeout, install a Time Manager task. */
761 if ( pb->ioSearchTime != 0 )
762 {
763 /* Start timer */
764 timerTask.theTask.tmAddr = NewTimerProc(TimeOutTask);
765 InsTime((QElemPtr)&(timerTask.theTask));
766 PrimeTime((QElemPtr)&(timerTask.theTask), pb->ioSearchTime);
767 }
768
769 /* Check the parameter block passed for things that we don't want to assume */
770 /* are OK later in the code. For example, make sure pointers to data structures */
771 /* and buffers are not NULL. And while we're in there, see if the request */
772 /* specified searching for files, directories, or both, and see if the search */
773 /* was by full or partial name. */
774 result = VerifyUserPB(pb, &includeFiles, &includeDirs, &includeNames);
775 if ( result == noErr )
776 {
777 pb->ioActMatchCount = 0; /* no matches yet */
778
779 if ( includeNames )
780 {
781 /* The search includes seach by full or partial name. */
782 /* Make an upper case copy of the match string to pass to */
783 /* CheckForMatches. */
784 BlockMoveData(pb->ioSearchInfo1->hFileInfo.ioNamePtr,
785 upperName,
786 pb->ioSearchInfo1->hFileInfo.ioNamePtr[0] + 1);
787 /* Use the same non-international call the File Manager uses */
788 UpperString(upperName, true);
789 }
790
791 /* Prevent casting to my type throughout code */
792 catPosition = (SearchPositionRecPtr)&pb->ioCatPosition;
793
794 /* Create searchStack first time called */
795 if ( searchStack == NULL )
796 {
797 searchStack = (LevelRecHandle)NewHandle(kAdditionalLevelRecs * sizeof(LevelRec));
798 }
799
800 /* Make sure searchStack really exists */
801 if ( searchStack != NULL )
802 {
803 searchStackSize = InlineGetHandleSize((Handle)searchStack);
804
805 /* See if the search is a new search or a resumed search. */
806 if ( catPosition->initialize == 0 )
807 {
808 /* New search. */
809
810 /* Get the real vRefNum and fill in catPosition->initialize. */
811 result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &catPosition->initialize);
812 if ( result == noErr )
813 {
814 /* clear searchStack */
815 catPosition->stackDepth = 0;
816
817 /* use dirID parameter passed and... */
818 index = -1; /* start with the passed directory itself! */
819 }
820 }
821 else
822 {
823 /* We're resuming a search. */
824
825 /* Get the real vRefNum and make sure catPosition->initialize is valid. */
826 result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &tempLong);
827 if ( result == noErr )
828 {
829 /* Make sure the resumed search is to the same volume! */
830 if ( catPosition->initialize == tempLong )
831 {
832 /* For resume, catPosition->stackDepth > 0 */
833 if ( catPosition->stackDepth > 0 )
834 {
835 /* Position catPosition->stackDepth to access last saved level */
836 --(catPosition->stackDepth);
837
838 /* Get the dirID and index for the next item */
839 dirID = (*searchStack)[catPosition->stackDepth].dirID;
840 index = (*searchStack)[catPosition->stackDepth].index;
841
842 /* Check the dir's mod date against the saved mode date on our "stack" */
843 modDate = GetDirModDate(realVRefNum, dirID);
844 if ( modDate != (*searchStack)[catPosition->stackDepth].dirModDate )
845 {
846 result = catChangedErr;
847 }
848 }
849 else
850 {
851 /* Invalid catPosition record was passed */
852 result = paramErr;
853 }
854 }
855 else
856 {
857 /* The volume is not the same */
858 result = catChangedErr;
859 }
860 }
861 }
862
863 if ( result == noErr )
864 {
865 /* ioNamePtr and ioVRefNum only need to be set up once. */
866 cPB.hFileInfo.ioNamePtr = itemName;
867 cPB.hFileInfo.ioVRefNum = realVRefNum;
868
869 /*
870 ** Here's the loop that:
871 ** Finds the next item on the volume.
872 ** If noErr, calls the code to check for matches and add matches
873 ** to the match buffer.
874 ** Sets up dirID and index for to find the next item on the volume.
875 **
876 ** The looping ends when:
877 ** (a) an unexpected error is returned by PBGetCatInfo. All that
878 ** is expected is noErr and fnfErr (after the last item in a
879 ** directory is found).
880 ** (b) the caller specified a timeout and our Time Manager task
881 ** has fired.
882 ** (c) the number of matches requested by the caller has been found.
883 ** (d) the last item on the volume was found.
884 */
885 do
886 {
887 /* get the next item */
888 cPB.hFileInfo.ioFDirIndex = index;
889 cPB.hFileInfo.ioDirID = dirID;
890 result = PBGetCatInfoSync(&cPB);
891 if ( index != -1 )
892 {
893 if ( result == noErr )
894 {
895 /* We found something */
896
897 CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs);
898
899 ++index;
900 if ( (cPB.dirInfo.ioFlAttrib & ioDirMask) != 0 )
901 {
902 /* It's a directory */
903
904 result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize);
905 if ( result == noErr )
906 {
907 /* Save the current state on the searchStack */
908 /* when we come back, this is where we'll start */
909 (*searchStack)[catPosition->stackDepth].dirID = dirID;
910 (*searchStack)[catPosition->stackDepth].index = index;
911 (*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID);
912
913 /* position catPosition->stackDepth for next saved level */
914 ++(catPosition->stackDepth);
915
916 /* The next item to get is the 1st item in the child directory */
917 dirID = cPB.dirInfo.ioDrDirID;
918 index = 1;
919 }
920 }
921 /* else do nothing for files */
922 }
923 else
924 {
925 /* End of directory found (or we had some error and that */
926 /* means we have to drop out of this directory). */
927 /* Restore last thing put on stack and */
928 /* see if we need to continue or quit. */
929 if ( catPosition->stackDepth > 0 )
930 {
931 /* position catPosition->stackDepth to access last saved level */
932 --(catPosition->stackDepth);
933
934 dirID = (*searchStack)[catPosition->stackDepth].dirID;
935 index = (*searchStack)[catPosition->stackDepth].index;
936
937 /* Check the dir's mod date against the saved mode date on our "stack" */
938 modDate = GetDirModDate(realVRefNum, dirID);
939 if ( modDate != (*searchStack)[catPosition->stackDepth].dirModDate )
940 {
941 result = catChangedErr;
942 }
943 else
944 {
945 /* Going back to ancestor directory. */
946 /* Clear error so we can continue. */
947 result = noErr;
948 }
949 }
950 else
951 {
952 /* We hit the bottom of the stack, so we'll let the */
953 /* the eofErr drop us out of the loop. */
954 result = eofErr;
955 }
956 }
957 }
958 else
959 {
960 /* Special case for index == -1; that means that we're starting */
961 /* a new search and so the first item to check is the directory */
962 /* passed to us. */
963 if ( result == noErr )
964 {
965 /* We found something */
966
967 CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs);
968
969 /* Now, set the index to 1 and then we're ready to look inside */
970 /* the passed directory. */
971 index = 1;
972 }
973 }
974 } while ( (!timerTask.stopSearch) && /* timer hasn't fired */
975 (result == noErr) && /* no unexpected errors */
976 (pb->ioReqMatchCount > pb->ioActMatchCount) ); /* we haven't found our limit */
977
978 /* Did we drop out of the loop because of timeout or */
979 /* ioReqMatchCount was found? */
980 if ( result == noErr )
981 {
982 result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize);
983 if ( result == noErr )
984 {
985 /* Either there was a timeout or ioReqMatchCount was reached. */
986 /* Save the dirID and index for the next time we're called. */
987
988 (*searchStack)[catPosition->stackDepth].dirID = dirID;
989 (*searchStack)[catPosition->stackDepth].index = index;
990 (*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID);
991
992 /* position catPosition->stackDepth for next saved level */
993
994 ++(catPosition->stackDepth);
995 }
996 }
997 }
998 }
999 else
1000 {
1001 /* searchStack Handle could not be allocated */
1002 result = memFullErr;
1003 }
1004 }
1005
1006 if ( pb->ioSearchTime != 0 )
1007 {
1008 /* Stop Time Manager task here if it was installed */
1009 RmvTime((QElemPtr)&(timerTask.theTask));
1010 DisposeRoutineDescriptor(timerTask.theTask.tmAddr);
1011 }
1012
1013 return ( result );
1014 }
1015
1016 /*****************************************************************************/
1017
1018 pascal OSErr PBCatSearchSyncCompat(CSParamPtr paramBlock)
1019 {
1020 static Boolean fullExtFSDispatchingtested = false;
1021 static Boolean hasFullExtFSDispatching = false;
1022 OSErr result;
1023 Boolean supportsCatSearch;
1024 long response;
1025 GetVolParmsInfoBuffer volParmsInfo;
1026 long infoSize;
1027
1028 result = noErr;
1029
1030 /* See if File Manager will pass CatSearch requests to external file systems */
1031 /* we'll store the results in a static variable so we don't have to call Gestalt */
1032 /* everytime we're called. */
1033 if ( !fullExtFSDispatchingtested )
1034 {
1035 fullExtFSDispatchingtested = true;
1036 if ( Gestalt(gestaltFSAttr, &response) == noErr )
1037 {
1038 hasFullExtFSDispatching = ((response & (1L << gestaltFullExtFSDispatching)) != 0);
1039 }
1040 }
1041
1042 /* CatSearch is a per volume attribute, so we have to check each time we're */
1043 /* called to see if it is available on the volume specified. */
1044 supportsCatSearch = false;
1045 if ( hasFullExtFSDispatching )
1046 {
1047 infoSize = sizeof(GetVolParmsInfoBuffer);
1048 result = HGetVolParms(paramBlock->ioNamePtr, paramBlock->ioVRefNum,
1049 &volParmsInfo, &infoSize);
1050 if ( result == noErr )
1051 {
1052 supportsCatSearch = hasCatSearch(volParmsInfo);
1053 }
1054 }
1055
1056 /* noErr or paramErr is OK here. */
1057 /* paramErr just means that GetVolParms isn't supported by this volume */
1058 if ( (result == noErr) || (result == paramErr) )
1059 {
1060 if ( supportsCatSearch )
1061 {
1062 /* Volume supports CatSearch so use it. */
1063 /* CatSearch is faster than an indexed search. */
1064 result = PBCatSearchSync(paramBlock);
1065 }
1066 else
1067 {
1068 /* Volume doesn't support CatSearch so */
1069 /* search using IndexedSearch from root directory. */
1070 result = IndexedSearch(paramBlock, fsRtDirID);
1071 }
1072 }
1073
1074 return ( result );
1075 }
1076
1077 /*****************************************************************************/
1078
1079 pascal OSErr NameFileSearch(ConstStr255Param volName,
1080 short vRefNum,
1081 ConstStr255Param fileName,
1082 FSSpecPtr matches,
1083 long reqMatchCount,
1084 long *actMatchCount,
1085 Boolean newSearch,
1086 Boolean partial)
1087 {
1088 CInfoPBRec searchInfo1, searchInfo2;
1089 HParamBlockRec pb;
1090 OSErr error;
1091 static CatPositionRec catPosition;
1092 static short lastVRefNum = 0;
1093
1094 /* get the real volume reference number */
1095 error = DetermineVRefNum(volName, vRefNum, &vRefNum);
1096 if ( error != noErr )
1097 return ( error );
1098
1099 pb.csParam.ioNamePtr = NULL;
1100 pb.csParam.ioVRefNum = vRefNum;
1101 pb.csParam.ioMatchPtr = matches;
1102 pb.csParam.ioReqMatchCount = reqMatchCount;
1103 if ( partial ) /* tell CatSearch what we're looking for: */
1104 {
1105 pb.csParam.ioSearchBits = fsSBPartialName + fsSBFlAttrib; /* partial name file matches or */
1106 }
1107 else
1108 {
1109 pb.csParam.ioSearchBits = fsSBFullName + fsSBFlAttrib; /* full name file matches */
1110 }
1111 pb.csParam.ioSearchInfo1 = &searchInfo1;
1112 pb.csParam.ioSearchInfo2 = &searchInfo2;
1113 pb.csParam.ioSearchTime = 0;
1114 if ( (newSearch) || /* If caller specified new search */
1115 (lastVRefNum != vRefNum) ) /* or if last search was to another volume, */
1116 {
1117 catPosition.initialize = 0; /* then search from beginning of catalog */
1118 }
1119 pb.csParam.ioCatPosition = catPosition;
1120 pb.csParam.ioOptBuffer = GetTempBuffer(0x00004000, &pb.csParam.ioOptBufSize);
1121
1122 /* search for fileName */
1123 searchInfo1.hFileInfo.ioNamePtr = (StringPtr)fileName;
1124 searchInfo2.hFileInfo.ioNamePtr = NULL;
1125
1126 /* only match files (not directories) */
1127 searchInfo1.hFileInfo.ioFlAttrib = 0x00;
1128 searchInfo2.hFileInfo.ioFlAttrib = ioDirMask;
1129
1130 error = PBCatSearchSyncCompat((CSParamPtr)&pb);
1131
1132 if ( (error == noErr) || /* If no errors or the end of catalog was */
1133 (error == eofErr) ) /* found, then the call was successful so */
1134 {
1135 *actMatchCount = pb.csParam.ioActMatchCount; /* return the match count */
1136 }
1137 else
1138 {
1139 *actMatchCount = 0; /* else no matches found */
1140 }
1141
1142 if ( (error == noErr) || /* If no errors */
1143 (error == catChangedErr) ) /* or there was a change in the catalog */
1144 {
1145 catPosition = pb.csParam.ioCatPosition;
1146 lastVRefNum = vRefNum;
1147 /* we can probably start the next search where we stopped this time */
1148 }
1149 else
1150 {
1151 catPosition.initialize = 0;
1152 /* start the next search from beginning of catalog */
1153 }
1154
1155 if ( pb.csParam.ioOptBuffer != NULL )
1156 {
1157 DisposePtr(pb.csParam.ioOptBuffer);
1158 }
1159
1160 return ( error );
1161 }
1162
1163 /*****************************************************************************/
1164
1165 pascal OSErr CreatorTypeFileSearch(ConstStr255Param volName,
1166 short vRefNum,
1167 OSType creator,
1168 OSType fileType,
1169 FSSpecPtr matches,
1170 long reqMatchCount,
1171 long *actMatchCount,
1172 Boolean newSearch)
1173 {
1174 CInfoPBRec searchInfo1, searchInfo2;
1175 HParamBlockRec pb;
1176 OSErr error;
1177 static CatPositionRec catPosition;
1178 static short lastVRefNum = 0;
1179
1180 /* get the real volume reference number */
1181 error = DetermineVRefNum(volName, vRefNum, &vRefNum);
1182 if ( error != noErr )
1183 return ( error );
1184
1185 pb.csParam.ioNamePtr = NULL;
1186 pb.csParam.ioVRefNum = vRefNum;
1187 pb.csParam.ioMatchPtr = matches;
1188 pb.csParam.ioReqMatchCount = reqMatchCount;
1189 pb.csParam.ioSearchBits = fsSBFlAttrib + fsSBFlFndrInfo; /* Looking for finder info file matches */
1190 pb.csParam.ioSearchInfo1 = &searchInfo1;
1191 pb.csParam.ioSearchInfo2 = &searchInfo2;
1192 pb.csParam.ioSearchTime = 0;
1193 if ( (newSearch) || /* If caller specified new search */
1194 (lastVRefNum != vRefNum) ) /* or if last search was to another volume, */
1195 {
1196 catPosition.initialize = 0; /* then search from beginning of catalog */
1197 }
1198 pb.csParam.ioCatPosition = catPosition;
1199 pb.csParam.ioOptBuffer = GetTempBuffer(0x00004000, &pb.csParam.ioOptBufSize);
1200
1201 /* no fileName */
1202 searchInfo1.hFileInfo.ioNamePtr = NULL;
1203 searchInfo2.hFileInfo.ioNamePtr = NULL;
1204
1205 /* only match files (not directories) */
1206 searchInfo1.hFileInfo.ioFlAttrib = 0x00;
1207 searchInfo2.hFileInfo.ioFlAttrib = ioDirMask;
1208
1209 /* search for creator; if creator = 0x00000000, ignore creator */
1210 searchInfo1.hFileInfo.ioFlFndrInfo.fdCreator = creator;
1211 if ( creator == (OSType)0x00000000 )
1212 {
1213 searchInfo2.hFileInfo.ioFlFndrInfo.fdCreator = (OSType)0x00000000;
1214 }
1215 else
1216 {
1217 searchInfo2.hFileInfo.ioFlFndrInfo.fdCreator = (OSType)0xffffffff;
1218 }
1219
1220 /* search for fileType; if fileType = 0x00000000, ignore fileType */
1221 searchInfo1.hFileInfo.ioFlFndrInfo.fdType = fileType;
1222 if ( fileType == (OSType)0x00000000 )
1223 {
1224 searchInfo2.hFileInfo.ioFlFndrInfo.fdType = (OSType)0x00000000;
1225 }
1226 else
1227 {
1228 searchInfo2.hFileInfo.ioFlFndrInfo.fdType = (OSType)0xffffffff;
1229 }
1230
1231 /* zero all other FInfo fields */
1232 searchInfo1.hFileInfo.ioFlFndrInfo.fdFlags = 0;
1233 searchInfo1.hFileInfo.ioFlFndrInfo.fdLocation.v = 0;
1234 searchInfo1.hFileInfo.ioFlFndrInfo.fdLocation.h = 0;
1235 searchInfo1.hFileInfo.ioFlFndrInfo.fdFldr = 0;
1236
1237 searchInfo2.hFileInfo.ioFlFndrInfo.fdFlags = 0;
1238 searchInfo2.hFileInfo.ioFlFndrInfo.fdLocation.v = 0;
1239 searchInfo2.hFileInfo.ioFlFndrInfo.fdLocation.h = 0;
1240 searchInfo2.hFileInfo.ioFlFndrInfo.fdFldr = 0;
1241
1242 error = PBCatSearchSyncCompat((CSParamPtr)&pb);
1243
1244 if ( (error == noErr) || /* If no errors or the end of catalog was */
1245 (error == eofErr) ) /* found, then the call was successful so */
1246 {
1247 *actMatchCount = pb.csParam.ioActMatchCount; /* return the match count */
1248 }
1249 else
1250 {
1251 *actMatchCount = 0; /* else no matches found */
1252 }
1253
1254 if ( (error == noErr) || /* If no errors */
1255 (error == catChangedErr) ) /* or there was a change in the catalog */
1256 {
1257 catPosition = pb.csParam.ioCatPosition;
1258 lastVRefNum = vRefNum;
1259 /* we can probably start the next search where we stopped this time */
1260 }
1261 else
1262 {
1263 catPosition.initialize = 0;
1264 /* start the next search from beginning of catalog */
1265 }
1266
1267 if ( pb.csParam.ioOptBuffer != NULL )
1268 {
1269 DisposePtr(pb.csParam.ioOptBuffer);
1270 }
1271
1272 return ( error );
1273 }
1274
1275 /*****************************************************************************/