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