; ; File: CatSearch.a ; ; Contains: High speed catalog search ; ; Written by: Dave N. Feldman ; ; Copyright: © 1988-1992 by Apple Computer, Inc., all rights reserved. ; ; Change History (most recent first): ; ; 4/15/92 kc Removed the "ROM" prefix from the RomBind routines. ; ¥ Pre-SuperMario comments follow ¥ ; <19> 9/13/91 JSM Cleanup header. ; <18> 2/25/91 dnf dho, #83584: Change the name of maskNotFileNames to ; maskNotSearchInfo2, since that what it really does. Update the ; comment in the #paramErr rules section to note the new bits ; that count here. ; <17> 2/12/91 dnf gbm, #breaksTheAUXExternalFileSystem: Make sure CatSearch ; clears A3 (WDCB pointer) when exiting, since we never identify ; working directories. ; <16> 11/28/90 dnf Put a comment in front of <14> ; <15> 11/28/90 dnf Put a comment in front of <14> ; <14> 11/27/90 dnf (with dba) Fix use of fsSBNegate when ioFlAttrib specifies only ; files or directories. ; <13> 11/6/90 dnf (with dba) Don't crush the timerInstalled flag. Make sure that ; long string target names (>31 characters) don't get searched. ; <12> 10/30/90 dnf (with dba) Start the timer way early for more accurate timing. ; Fix early error exit cases where a3 wasn't set up. ; <11> 8/28/90 dnf Rename ioSpecBits, ioSpec1 and ioSpec2 to ioSearchBits, ; ioSearchInfo1 and ioSearchInfo2 ; <10> 7/30/90 dnf Convert to linked patch; change rom reference names and use ; jsrRom macro. ; <9> 7/2/90 dnf Fix typo in branch from FlXFndrInfoIsDir case. ; <8> 6/2/90 dnf Use proper offsets for both file and directory search cases on ; finder info and dates ; <7> 4/17/90 dnf Fix register trashing bug in StartTimer ; <6> 3/16/90 dnf Return FSSpecs in ioMatchPtr instead of MBufEntries ; <5> 2/26/90 dnf Change ioQuant from a catalog record count to an amount of time ; to let the call run (and rename it ioSearchTime) ; <4> 2/8/90 dnf Test d0 before exiting CheckCriteria ; <3> 2/4/90 dnf Added negate bit; fixed ioFlAttrib bug; Changed to include ; StandardEqu.d ; <2> 1/9/90 dnf Add support for search on parent ID, implement CheckCSPB ; routine. ; <1.5> 9/7/89 dnf Added support for CatPosition ; <1.4> 8/26/89 dnf Get minimum buffer off of stack ; <1.3> 7/31/89 dnf Bug fixes, input from code review ; <1.2> 7/6/89 dnf Added read buffer support and offline check ; <1.1> 6/1/89 GGD Added include of HFS70Equ.a so that it would assemble. ; <1.0> 5/30/89 dnf Integrate CatSearch, FileID's and Desktop Database Mgr into one ; ptch ; 1/23/89 dnf Modified for new param block and search criteria ; 12/20/88 dnf Broke off BTPScan into separate file ; 11/2/88 dnf New Today. ; ; External ; Routine: CMCatSearch - Searches catalog for files according to search criteria ; ; Internal GetReadBuffer - Allocates a read buffer ; Subroutines: MbufInsert - Inserts a match record into match buffer ; UpperCaseIt - Convert a CName to its canonical form for comparison ; CompareMasked - Masked bitwise comparison of a range of bytes ; CompareRange - See if a value falls within a specified range ; CheckCriteria - Checks current CNode against all specified search criteria ; ; TimerRoutine - timer task completion routine ; StartTimer - installs a timer task ; StopTimer - removes a timer task ; print push PRINT OFF LOAD 'StandardEqu.d' INCLUDE 'CatSrchPriv.a' INCLUDE 'BTScanPriv.a' ; for the NoMoreReads bit include 'LinkedPatchMacros.a' include 'FileMgrPrivate.a' PRINT pop import BTIPScan, BTEndPScan, BTGetPhys ;__________________________________________________________________________________ ; ; Routine: GetReadBuffer ; ; Function: Allocates a read buffer ; ; Input: A3.L - CatSearch state record pointer ; A5.L - user pb pointer ; ; Output: A1.L - address of buffer ; D3.L - length (in bytes) of buffer ; ; The deal - If the ioRBufPtr field in the param block points to a suitable buffer ; that buffer will be used in the call. If not, CatSearch will get a small (512-byte or so) ; buffer off the A6 stack. A buffer is suitable if it is bigger than the one CatSearch ; would get off the stack. ;__________________________________________________________________________________ GetReadBuffer: proc import bufAddr, bufLength MOVE.L ioOptBuffer(A5), D3 ; get the user's buffer address <1.2> BEQ.S @getOurOwn ; nil? Use our own buffer then <1.2> MOVE.L ioOptBufSize(A5), D3 ; get the user's buffer length <1.2> CMP.L #CSMinBufferSize, D3 ; bigger than ours? <1.2> BLE.S @getOurOwn ; if not, we'll use ours <1.2> ; the user's buffer is cool; go ahead and use it <1.2> MOVE.L ioOptBuffer(A5), A1 ; move buffer address to output register <1.2> BRA.S @GRBExit ; <1.2> ; the user's buffer is uncool. Get our own off the A6 stack. <1.4> @getOurOwn: ; <1.2> MOVE.L #CSMinBufferSize, D3 ; length of read buffer <1.4> SUBA.L D3, A6 ; get memory off the stack <1.4> MOVEA.L A6, A1 ; address of read buffer <1.4> BSET.B #bufferOnStack, CSSR.flags(A3) ; remember so we can deallocate <1.4> @GRBExit: RTS _CSDebug 'GetReadBuffer', 0 endproc ; ; How the timer works here: ; ; Before reading any data from the catalog, we start a timer running. ; When the timer expires the timer completion routine will set the timerFired bit ; in the CatSearch state record. This is passed along to the BTree scanner in the ; "noMoreReads" bit, telling the scanner to continue to deliver records until it ; needs to hit the disk again. ; ; The effect is that CatSearch will process records until the timer goes off, ; at which point it will finish the records it's got in memory and exit ; ;__________________________________________________________________________________ ; ; Routine: TimerRoutine ; ; Function: Gets run when the timer goes off ; Sets the "noMoreReads" bit in the PScan flags ; ; Input: A1 - csTimeTask record (a.k.a CatSearch state record pointer) ; ; Output: A1 - trashed ; ; Note that since csTimeTask is first in the CSSR, a1 can be used like a pointer ; to the CSSR. ;__________________________________________________________________________________ TimerRoutine: proc bset.b #timerFired, CSSR.flags(a1) ; boom! <12> rts endproc ;__________________________________________________________________________________ ; ; Routine: StartTimer ; ; Function: Checks to see if we need to run a timer during this search ; If so, starts the timer ; ; Input: A3.L - CatSearch state record pointer ; D0.L - Time count (time manager form) or zero for no timer ; ; Output: A3.L - CatSearch state record pointer ; ; If the time count is zero, no timer task is started. If a timer task is started ; the "timerWasInstalled" bit is set in CSSR.flags. ;__________________________________________________________________________________ StartTimerRegs reg d0-d1/a0-a1 StartTimer: proc tst.l d0 ; do we need to start a timer? beq.s @StartTimerExit ; if count = 0, no. movem.l StartTimerRegs, -(sp) move.l d0, d1 ; save time value around _InsTime bset.b #timerWasInstalled, CSSR.flags(a3) ; note that we're doing this move.l a3, a0 ; a0 = ptr (our time task record) lea.l TimerRoutine, a1 ; a1 = ptr(our timer completion routine) move.l a1, tmAddr(a0) ; point to our completion routine _InsTime ; put our task in the queue move.l d1, d0 ; restore time value _PrimeTime ; start the timer ticking movem.l (sp)+, StartTimerRegs @StartTimerExit: rts endproc ;__________________________________________________________________________________ ; ; Routine: StopTimer ; ; Function: Checks to see if we installed a time manager task ; If so, removes it. ; ; Input: A3.L - CatSearch state record pointer ; ; Output: A3.L - CatSearch state record pointer ; ;__________________________________________________________________________________ StopTimerRegs reg d0/a0 StopTimer: proc btst.b #timerWasInstalled, CSSR.flags(a3) ; did we use a timer this search? beq.s @noTimerUsed movem.l StopTimerRegs, -(sp) move.l a3, a0 ; a0 = ptr (our time task record) _RmvTime ; remove the task from the queue movem.l (sp)+, StopTimerRegs @noTimerUsed: rts endproc ;__________________________________________________________________________________ ; ; Routine: MBufInsert ; ; Function: Convert the current record into an FSSpec and insert it ; into the Match Buffer ; Increments nextMBuf pointer in CSSR ; ; Input: A0.L - ptr(catalog key) ; A3.L - ptr(CatSearch state record) ; ; Output: ; A0.L - trash ; A1.L - trash ; D0.L - trash ;__________________________________________________________________________________ MBufInsert: proc MOVE.L CSSR.PSRPtr(A3), A1 ; A1 = ptr(Pscan state record) MOVE.L PSR.fcbptr(A1), A1 ; A1 = ptr(fcb of btree file we're scanning) MOVE.L fcbVPtr(A1), A1 ; A1 = ptr(vcb of search volume) MOVE.W vcbVRefNum(A1), D0 ; D0 = vRefNum of search volume MOVE.L CSSR.nextMBuf(A3), A1 ; A1 = ptr(next empty MBuf entry) MOVE.W D0, (A1)+ ; copy vRefNum into FSSpec MOVE.L ckrParID(A0), (A1)+ ; copy ParID from key MOVEQ.L #0, D0 ; clear D0 to clear high bytes LEA.L ckrCName(A0), A0 ; A0 = ptr(str31 containing CName) MOVE.B (A0), D0 ; get length of CName ; don't add one for the length byte 'cause we're DBRAing <1.3> @MoveCName: MOVE.B (A0)+, (A1)+ ; move the CName, byte by byte DBRA D0, @MoveCName ; (this is still faster than _BlockMove) MOVE.L CSSR.nextMBuf(A3), A1 ; A1 = ptr(just filled MBuf entry) ADDA.W #FSSpec.size, A1 ; A1 = ptr(MBuf entry after the one we just filled) MOVE.L A1, CSSR.nextMBuf(A3) ; remember position for next time RTS _CSDebug 'MBufInsert', 0 ; _AssumeEQ ckrParID+4, ckrCName ; put this in. ¥¥ endproc ;__________________________________________________________________________________ ; ; UpperCaseIt ; ; Function: Convert the current cName (from the key to its catalog record) ; into a canonical form for comparison. Store the result in the CatSearch ; state record. Zero flag up if current cName has length of zero. ; ; Register ; Usage: A0.L - ptr(key) ; A3.L - ptr(CatSearch state record) ; A4.L, A5.L - scratch ; ; D5.L - ioSearchBits ; ; Output: zero flag high if current cName is empty ; D0.L - D4.L are trashed ;__________________________________________________________________________________ UpperCaseIt: proc MOVE.L A0, D2 ; save address regs into trashable D regs MOVE.L A4, D3 MOVE.L A5, D4 MOVEQ.L #0, D0 ; clear D0 to clear byte 1 (for DBRA) MOVE.B ckrCName(A0), D0 ; D0.W = len(current cName) BEQ.S @failUCI ; null cNames fail MOVE.W D0, D1 ; keep another copy in D1 ; Don't add one for the length byte, 'cause we're DBRAing LEA.L ckrCName(A0), A4 ; A4 = ptr(current cName) LEA.L CSSR.copyCur(A3), A5 ; A5 = ptr(copy of current cName) @block MOVE.B (A4)+, (A5)+ ; move a byte DBRA D0, @block ; until we're done MOVE.W D1, D0 ; D0.W = len(copy of current cName) LEA.L CSSR.copyCur+1(A3), A0 ; A0 = ptr(1st char of copy of cName) _FSUprString ; uppercase, strip diacriticals MOVEQ.L #1, D0 ; just something to clear the zero flag ; Zero flag is cool regardless of how we got here @failUCI MOVEA.L D2, A0 ; restore A regs from D regs MOVEA.L D3, A4 MOVEA.L D4, A5 RTS _CSDebug 'UpperCaseIt', 0 endproc ;__________________________________________________________________________________ ; ; CompareMasked ; ; Function: Do a bitwise comparison with mask. The source and data are exclusive-or'ed ; together, yielding 1's where they differ. That value is and'ed with the ; comparison mask, leaving a word with 1's where there are significant ; mismatches. This technique allows the caller to set target bits only where ; the mask bits are 1, and leaving the rest undefined. If all long words ; pass, then the zero flag is set on return. ; ; Register ; Usage: A0.L - ptr(source data) (what you're checking out) ; A4.L - ptr(target data) (what you're looking for) ; A5.L - ptr(comparison mask) (what bits you care about) ; ; D0.W - comparison count - 1 (note: we step by longwords) ; D1.L - trashed ; D2.L - trashed ; ; Output: zero flag high if hit ; ; Note: This routine steps on the register usage of CheckCriteria by using A0 as an input. ;__________________________________________________________________________________ CompareMasked: proc MOVE.L (A0)+, D1 ; D1 = long word of source data MOVE.L (A4)+, D2 ; D2 = long of target data EOR.L D2, D1 ; D1 bits are on where source and data differ AND.L (A5)+, D1 ; D1 bits are on where we cared about mismatches DBNE D0, CompareMasked ; keep going as long as the bytes match RTS _CSDebug 'CompareMasked', 0 endproc ;__________________________________________________________________________________ ; ; CompareRange ; ; Function: Compare to see if ioSearchInfo1[D1].L <= D0.L <= ioSearchInfo2[D1].L ; Return carry clear if yes, carry set if not. ; ; Register ; Usage: A0.L - ptr(ioSearchInfo1) (low end of range) ; A2.L - ptr(ioSearchInfo2) (high end of range) ; ; D0.L - current value ; D1.W - offset in CInfoPBRec to use for value ; ; Output: zero flag high if D0 is in range, low if not ; ;__________________________________________________________________________________ CompareRange: proc CMP.L (A0, D1.W), D0 ; is D0 >= low end of range? BLO.S @failRange ; if (D0 < value in ioSearchInfo1), fail CMP.L (A2, D1.W), D0 ; is D0 <= high end of range? BLS.S @succeedRange ; if (D0 <= value in ioSearchInfo2), succeed @failRange: MOVEQ.L #1, D0 ; clear the zero flag RTS @succeedRange: MOVEQ.L #0, D0 ; set the zero flag RTS _CSDebug 'CompareRange', 0 endproc ;__________________________________________________________________________________ ; ; Routine: CheckCriteria ; ; Function: Checks current record against match criteria. ; Partial and full name matches are the optimized cases ; ; Input: A0.L - ptr(key) ; A1.L - ptr(data record) ; A3.L - ptr(CatSearch state record) ; A5.L - ptr(user parameter block) ; D5.L - ioSearchBits ; ; Output: A0.L - ptr(key) ; A1.L, A2.L - trash ; A3.L, A4.L, A5.L - preserved ; D0, D1, D2, D3, D4 - trash ; D5, D6, D7 - preserved ; zero flag set if match, clear otherwise ;__________________________________________________________________________________ CheckCriteria: proc MOVE.L A0, -(A6) CMP.B #cmFilCN, cdrType(A1) ; is this a file record? <14> BEQ.S @IsFile ; <14> CMP.B #cmDirCN, cdrType(A1) ; is this a directory record? <14> BNE @notSearchable ; we only consider files and directories <14> BCLR.B #isFile, CSSR.flags(A3) ; indicate that current record is a dir <14> BTST.B #inclDirs, CSSR.flags(A3) ; do criteria include directories? <14> BEQ @failed ; if not, we're done already <14> BRA.S @LookAtIt ; if so, continue with criteria <14> @IsFile: BSET.B #isFile, CSSR.flags(A3) ; indicate that current record is a file BTST.B #inclFiles, CSSR.flags(A3) ; do criteria include files? BEQ @failed ; if not, we're done already @LookAtIt: BTST.L #fsSBPartialName, D5 ; is name substring included? BEQ.S @FullName ; go check the next thing (Exact cName match) ; This is the 'name substring' match BSR UpperCaseIt ; make an uppercase copy of the current cName BEQ @failed ; zero flag indicates current cName is empty ; Register use during substring compare ; A0 - ptr(current cName) D1 - starting point limit in current cName ; A1 - ptr(substring) D2 - length of substring ; A2 - scratch for DBRA D3 - next starting point in current cName ; A3 - scratch for DBRA D4 - scratch for DBNE MOVEM.L A0/A1/A3, -(A6) ; save regs around string compare loop LEA.L CSSR.copyCur(A3), A0 ; A0 = ptr(current cName) LEA.L CSSR.copyTarg(A3), A1 ; A1 = ptr(target substring) MOVE.B (A0), D1 ; D1 = len(current cName) MOVEQ.L #0, D2 ; make D2 clean for DBRA MOVE.B (A1)+, D2 ; D2 = len(target substring) (A1 = ptr(1st char)) SUB.B D2, D1 ; D1 = -1 + last possible starting point for a match BMI.S @failPartial ; if that's < 0, we couldn't possibly match SUBQ.L #1, D2 ; adjust D2 for DBRA ADDQ.B #1, D1 ; D1 = last possible starting point for a match MOVEQ.L #1, D3 ; initialize "next starting point" value (past length byte) @outer: LEA.L (A0, D3), A2 ; A2 = ptr(place to look for match) MOVEA.L A1, A3 ; A3 = ptr(1st char in substring) MOVE.W D2, D4 ; D4 counts the DBRA @loop: CMPM.B (A2)+, (A3)+ ; compare char by char DBNE D4, @loop ; and keep going as long as they're the same BEQ.S @hitPartial ; all the way without a miss = a hit @again: ADDQ.B #1, D3 ; up the starting point CMP.B D1, D3 ; is starting point beyond last possible start? BLS.S @outer ; as long as it's not, check another starting point ; if we fell through to here, we missed on all starting points @failPartial MOVEM.L (A6)+, A0/A1/A3 ; restore regs saved around substring compare BRA @failed @hitPartial MOVEM.L (A6)+, A0/A1/A3 ; restore regs saved around substring compare MOVEQ.L #maskPartialName, D0 ; D0 = ioSearchBits long w/fsSBPartialName set CMP.L D0, D5 ; was PartialName the only criteria? BEQ @hit ; then leave now @FullName: ; This is the 'full cName' match BTST.L #fsSBFullName, D5 ; is full name included? BEQ.S @FlParID ; go check the next thing (file parent) BSR UpperCaseIt ; make an uppercase copy of the current cName MOVE.L A4, D1 ; save A4 around string compare LEA.L CSSR.copyCur(A3), A2 ; A2 = ptr(current cName) LEA.L CSSR.copyTarg(A3), A4 ; A4 = ptr(target string) MOVE.B (A2), D0 ; D0 = length of current CName (+ length byte for DBRA) @FullNameLoop: CMPM.B (A2)+, (A4)+ ; compare a byte DBNE D0, @FullNameLoop BNE.S @failedFull ; did we end on a missed character? ; If we fell through to here, we hit MOVEA.L D1, A4 ; restore reg saved around string compare MOVEQ.L #maskFullName, D0 ; D0 = ioSearchBits long w/fsSBFullName set CMP.L D0, D5 ; was Fullname the only criteria? BEQ @hit ; then leave now BRA.S @FlParID ; or else keep going with the next criteria @failedFull: MOVEA.L D1, A4 ; restore reg saved around string compare BRA @failed @FlParID: ; This is the parent ID check MOVE.L ckrParID(A0), D0 ; D0 = parent ID of current cNode (grabbed before we change A0) ; from here down, ; A0 - points to ioSearchInfo1 ; A2 - points to ioSearchInfo2 MOVEA.L ioSearchInfo1(A5), A0 ; A0 = ptr(CInfoPBRec with values & low end of ranges) MOVEA.L ioSearchInfo2(A5), A2 ; A2 = ptr(CInfoPBRec with masks & high end of ranges) BTST.L #fsSBFlParID, D5 ; is parent ID included? BEQ.S @FlAttrib ; go check the next thing (file attributes) MOVEQ.L #ioFlParID, D1 ; D1 = offset into CInfoPBRec to parent ID BSR CompareRange BNE @failed @FlAttrib: ; This is the file attributes check BTST.L #fsSBFlAttrib, D5 ; are file attributes included? BEQ.S @DrNmFls ; go check the next thing (files/directory) MOVE.B filFlags(A1), D0 ; D0 = file flags (bit 0 = locked is the only one) MOVE.B ioFlAttrib(A0), D1 ; D1 = user's target for file attrib bits EOR.B D1, D0 ; D0 bits on where source and target differ AND.B CSSR.attribMask(A3), D0 ; D0 bits on where we cared about mismatches BNE @failed @DrNmFls: BTST.L #fsSBDrNmFls, D5 ; is # files per directory included? BEQ.S @FlFndrInfo ; go check the next thing (finder info) MOVE.W dirVal(A1), D0 ; D0 = # files in current directory CMP.W ioDrNmFls(A0), D0 ; is D0 >= low end of range? BCS @failed ; if (D0 < # files in this directory), fail CMP.W ioDrNmFls(A2), D0 ; is D0 <= high end of range? BLS.S @FlFndrInfo ; if (D0 <= # files), continue with next criteria BRA @failed ; D0 too high; fail @FlFndrInfo: BTST.L #fsSBFlFndrInfo, D5 ; is finder info included? BEQ.S @FlXFndrInfo ; go check the next thing (extra finder info) MOVEM.L A0/A4/A5, -(A6) ; save A0/A4/A5 around test MOVEQ.L #(FlFndrInfoLen/4-1), D0; D0 = DBRA count for # of finder info bytes LEA.L ioFlUsrWds(A0), A4 ; A4 = ptr(target user words) LEA.L ioFlUsrWds(A2), A5 ; A5 = ptr(mask for user words) BTST.B #isFile, CSSR.flags(A3) ; are we looking at a file record? BEQ.S @FlFndrInfoIsDir ; if not, go get the right offset for a directory LEA.L filUsrWds(A1), A0 ; A0 = ptr(source data) BRA.S @1 @FlFndrInfoIsDir: LEA.L dirUsrInfo(A1), A0 ; A0 = ptr(source data) @1: BSR CompareMasked ; see if they match MOVEM.L (A6)+, A0/A4/A5 ; restore A0/A4/A5 BNE @failed @FlXFndrInfo: BTST.L #fsSBFlXFndrInfo, D5 ; is extra finder info included? BEQ.S @FlLgLen ; go check the next thing (file logical length) MOVEM.L A0/A4/A5, -(A6) ; save A0/A4/A5 around test MOVEQ.L #(FlXFndrInfoLen/4-1), D0; D0 = DBRA count for # of Xfinder info bytes LEA.L ioFlxFndrInfo(A0), A4 ; A4 = ptr(target Xfinder info) LEA.L ioFlxFndrInfo(A2), A5 ; A5 = ptr(mask Xfinder words) BTST.B #isFile, CSSR.flags(A3) ; are we looking at a file record? BEQ.S @FlXFndrInfoIsDir ; if not, go get the right offset for a directory LEA.L filFndrInfo(A1), A0 ; A0 = ptr(source data) BRA.S @2 @FlXFndrInfoIsDir: LEA.L dirFndrInfo(A1), A0 ; A0 = ptr(source data) @2: BSR CompareMasked ; see if they match MOVEM.L (A6)+, A0/A4/A5 ; restore A0/A4/A5 BNE @failed @FlLgLen: BTST.L #fsSBFlLgLen, D5 ; is logical length included? BEQ.S @FlPyLen ; go check the next thing (physical length) MOVE.L filLgLen(A1), D0 ; D0 = logical length of current file MOVEQ.L #ioFlLgLen, D1 ; D1 = offset into CInfoPBRec to find logical length BSR CompareRange BNE @failed @FlPyLen: BTST.L #fsSBFlPyLen, D5 ; is physical length included? BEQ.S @FlRLgLen ; go check the next thing (resource logical length) MOVE.L filPyLen(A1), D0 ; D0 = physical length of current file MOVEQ.L #ioFlPyLen, D1 ; D1 = offset into CInfoPBRec to find physical length BSR CompareRange BNE @failed @FlRLgLen: BTST.L #fsSBFlRLgLen, D5 ; is resource logical length included? BEQ.S @FlRPyLen ; go check the next thing (resource physical length) MOVE.L filRLgLen(A1), D0 ; D0 = resource logical length of current file MOVEQ.L #ioFlRLgLen, D1 ; D1 = offset into CInfoPBRec to find resource logical length BSR CompareRange BNE.S @failed @FlRPyLen: BTST.L #fsSBFlRPyLen, D5 ; is resource physical length included? BEQ.S @FlCrDat ; go check the next thing (file create date) MOVE.L filRPyLen(A1), D0 ; D0 = resource physical length of current file MOVEQ.L #ioFlRPyLen, D1 ; D1 = offset into CInfoPBRec to find resource physical length BSR CompareRange BNE.S @failed @FlCrDat: BTST.L #fsSBFlCrDat, D5 ; is creation date included? BEQ.S @FlMdDat ; go check the next thing (file modification date) BTST.B #isFile, CSSR.flags(A3) ; are we looking at a file record? BEQ.S @FlCrDatIsDir ; if not, go get the right offset for a directory MOVE.L filCrDat(A1), D0 ; D0 = creation date of current cNode BRA.S @3 @FlCrDatIsDir: MOVE.L dirCrDat(A1), D0 ; D0 = creation date of current cNode @3: MOVEQ.L #ioFlCrDat, D1 ; D1 = offset into CInfoPBRec to find creation date BSR CompareRange BNE.S @failed @FlMdDat: BTST.L #fsSBFlMdDat, D5 ; is modification date included? BEQ.S @FlBkDat ; go check the next thing (file backup date) BTST.B #isFile, CSSR.flags(A3) ; are we looking at a file record? BEQ.S @FlMdDatIsDir ; if not, go get the right offset for a directory MOVE.L filMdDat(A1), D0 ; D0 = modification date of current cNode BRA.S @4 @FlMdDatIsDir: MOVE.L dirMdDat(A1), D0 ; D0 = modification date of the current cNode @4: MOVEQ.L #ioFlMdDat, D1 ; D1 = offset into CInfoPBRec to find modification date BSR CompareRange BNE.S @failed @FlBkDat: BTST.L #fsSBFlBkDat, D5 ; is backup date included? BEQ.S @hit ; that's the last one, so we hit BTST.B #isFile, CSSR.flags(A3) ; are we looking at a file record? BEQ.S @FlBkDatIsDir ; if not, go get the right offset for a directory MOVE.L filBkDat(A1), D0 ; D0 = backup date of current cNode BRA.S @5 @FlBkDatIsDir: MOVE.L dirBkDat(A1), D0 ; D0 = backup date of current cNode @5: MOVEQ.L #ioFlBkDat, D1 ; D1 = offset into CInfoPBRec to find backup date BSR CompareRange BEQ.S @hit @failed: MOVEQ.L #1, D0 ; indicate failure BRA.S @CheckNegate @hit: MOVEQ.L #0, D0 ; indicate success @checkNegate: btst.l #fsSBNegate, d5 ; do we want everything that doesn't match? beq.s @CompareExit ; no; the result stands eor.b #1, d0 ; reverse the outcome bra.s @compareExit @notSearchable: ; not a file or directory record moveq.l #1, d0 ; indicate failure @CompareExit: tst.w d0 ; set Z to indicate success MOVEA.L (A6)+, A0 RTS _CSDebug 'CheckCriteria', 0 endproc ;__________________________________________________________________________________ ; ; Routine: VerifyCSPB ; ; Function: Verify that a param block contains legal values ; ; Input: A0.L - parameter block pointer ; ; Register Usage (during call): ; A0.L - pointer to user's param block D0.L - scratch ; A1.L - pointer to ioSearchInfo1 D1.L - scratch ; D2.B - copy of user's ioFlAttrib mask ; D3.L - cssr.flags byte ; A4.L - pointer to ioSearchInfo2 ; A5.L - pointer to user's param block D5.L - ioSearchBits ; ; Output: ; d0.W - result code (noErr, paramErr) ; d2.b - copy of user's ioFlAttrib mask, ioDirFlg masked ; d3.b - cssr.flags with inclFiles, inclDirs and inclNames set correctly ; ;__________________________________________________________________________________ VerifyCSPBRegs reg A1/A4-A5/D1/D5 VerifyCSPB: proc export movem.l VerifyCSPBRegs, -(sp) MOVE.L a0, a5 ; a5 = ptr(user's pb) ; look for conditions that return paramErr TST.L ioMatchPtr(A5) ; Nil mBuf? (P1) BEQ @ParamErrExit ; is a param err TST.L ioSearchInfo1(A5) ; Nil ioSearchInfo1? (P2) BEQ @ParamErrExit ; is a param err MOVE.L ioSearchBits(A5), D5 ; D5 = spec mask MOVE.L D5, D1 ; D1 = scratch copy of spec mask ANDI.L #maskNotSearchInfo2, D1 ; any criteria that requires ioSearchInfo2? (P3) BEQ.S @checkNamesIncluded ; if not, we don't worry about ioSearchInfo2 TST.L ioSearchInfo2(A5) ; Nil ioSearchInfo2? BEQ @ParamErrExit ; is a param err @checkNamesIncluded: ; If the search includes names, make a note of it and check to make sure that the name ; is non-nil and non-zero length. clr.b d3 ; d3 will become cssr.flags byte BTST.L #fsSBPartialName, D5 ; does search includes partial name? BNE.S @cNameCheck ; then go check it BTST.L #fsSBFullName, D5 ; does search includes full name? BEQ.S @checkSpecBits ; no? then don't need to copy name @cNameCheck: ; make sure name is not nil or zero length MOVEA.L ioSearchInfo1(A5), A1 ; A1 = ptr(ioSearchInfo1) MOVEA.L ioFileName(A1), A1 ; A1 = ptr(ioName) MOVE.L A1, D2 ; nil ioNamePtr? (P4) BEQ @ParamErrExit ; is a param err MOVEQ.L #0, D0 ; clear D0 to clear high bytes MOVE.B (A1), D0 ; D0 = len(target cName) BEQ @ParamErrExit ; zero length target name is a param err(P5) BSET.L #inclNames, d3 ; note that the search includes names @checkSpecBits: MOVEA.L ioSearchInfo1(A5), A1 ; A1 = ptr(ioSearchInfo1) MOVEA.L ioSearchInfo2(A5), A4 ; A4 = ptr(ioSearchInfo2) BTST.L #fsSBFlAttrib, D5 ; are file attributes included in the search? BEQ.S @includeBoth ; if not, search is on both files and directories move.b ioFlAttrib(a4), d2 ; d2.b = user's attribute mask MOVE.B #maskBadAttribs, D0 ; D0 = illegal attribute bits on (P6) AND.B ioFlAttrib(A4), D0 ; are there any illegal attribute bits included? BNE @ParamErrExit ; then it's a param err BTST.L #ioDirFlg, d2 ; is the mask bit for directories on? BEQ.S @includeBoth ; no, which means to include both BTST.B #ioDirFlg, ioFlAttrib(A1) ; what's the desired value for the directory bit? BEQ.S @justFiles ; caller wants it off, i.e. just files BSET.L #inclDirs, d3 ; caller wants it on, i.e. just directories BRA.S @clearMaskBit ; we're done deciding about files vs. directories @includeBoth: BSET.L #inclDirs, d3 ; the search includes directories @justFiles: BSET.L #inclFiles, d3 ; the search includes files @clearMaskBit BCLR.L #ioDirFlg, d2 ; clear the dir bit; it isn't actually on disk @P7: ; (P7) BTST.L #inclDirs, d3 ; are directories included in the search? BEQ.S @P8 ; if not, don't worry about rule 7 BTST.L #fsSBFlAttrib, D5 ; does the search include file attributes? BEQ.S @P8 ; if not, don't worry about rule 7 BTST.B #ioLockFlg, ioFlAttrib(A4) ; did caller care about the locked bit? BNE.S @ParamErrExit ; locked attribute is meaningless on a directory @P8: ; (P8) BTST.L #fsSBDrNmFls, D5 ; is #files per directory included? BEQ.S @P9 ; then can't conflict with files BTST.L #inclFiles, d3 ; does the search include files? BNE.S @ParamErrExit ; then that's a param error @P9: ; (P9-P12) BTST.L #inclDirs, d3 ; are directories included in the search? BEQ.S @OKExit ; if not, don't worry about rules 9-12 MOVE.L D5, D0 ; make a copy of ioSearchBits ANDI.L #maskLengthBits, D0 ; kill all but the bits specifying some sort of length BNE.S @ParamErrExit ; any length spec is meaningless in a directory @OKExit: moveq.l #noErr, d0 bra.s @Exit @ParamErrExit: move.w #paramErr, d0 @Exit: movem.l (sp)+, VerifyCSPBRegs rts endproc ; Here are the parameter checking rules for PBCatSearch: <1.3> ; The labels at the left also appear in the code which checks that rule. ; You get a paramErr if ; P1 ioMBufPtr is nil ; P2 ioSearchInfo1 is nil ; P3 (ioSearchBits includes not(fsSBPartialName or fsSBFullName or fsSBNegate)) and ioSearchInfo2 is nil; ; i.e. A selected search bit requires the presence of ioSearchInfo2 ; ; Then these, ; ioSearchBits rules: You get a paramErr if ; P4 fsSBPartialName ; and (ioSearchInfo1->ioNamePtr = (nil or empty string)) ; P5 fsSBFullName (same as above) ; P6 fsSBFlAttrib and some bit other than 0 (locked) or 4 (directory) returns paramErr ; P7 search includes directories and ; (ioSearchBits includes fsSBFlAttrib) and (ioSearchInfo2^.ioFlAttrib bit 0 = 1) ; return paramErr ; (i.e. "locked directory" makes no sense) ; P8 fsSBDrNmFls and files returns paramErr ; P9 fsSBFlLgLen and directories returns paramErr ; P10 fsSBFlPyLen (same as above) ; P11 fsSBFlRLgLen (same as above) ; P12 fsSBFlRPyLen (same as above) ; ; A search includes (files only) if ; ioSearchBits includes fsSBFlAttrib ; - and - ; ioSearchInfo1^.ioFlAttrib bit 4 = 0 i.e. explicitly looking for non-directories ; - and - ; ioSearchInfo2^.ioFlAttrib bit 4 = 1 ; ; A search includes (directories only) if ; ioSearchBits includes fsSBFlAttrib ; - and - ; ioSearchInfo1^.ioFlAttrib bit 4 = 1 i.e. explicitly looking for directories ; - and - ; ioSearchInfo2^.ioFlAttrib bit 4 = 1 ; A search includes both if ; ioSearchBits includes fsSBFlAttrib ; - and - ; ioSearchInfo2^.ioFlAttrib bit 4 = 0 i.e. don't care about the directory bit ; ; - OR - ; ioSearchBits doesn't include fsSBFlAttrib i.e. don't care about attributes at all ;__________________________________________________________________________________ ; ; Routine: CheckCSPB ; ; Function: Check that a param block contains legal values ; This is a wrapper of VerifyCSPB that is set up as an ; unqueued a060 call. Its main purpose in life is to let ; external file systems get a quick parameter block check ; on a CatSearch call. ; ; (Unqueued a060 call means that this is pretty much a register-based ; OS trap. ) ; ; Input: A0.L - parameter block pointer ; ; Output: D0.W - noErr or paramErr ;__________________________________________________________________________________ CheckCSPBRegs reg d2/d3 ; the output registers from VerifyCSPB CheckCSPB proc export movem.l CheckCSPBRegs, -(sp) bsr VerifyCSPB movem.l (sp)+, CheckCSPBRegs rts endproc ;__________________________________________________________________________________ ; ; Routine: CMCatSearch ; ; Function: Locates files by search criteria ; ; Input: A0.L - parameter block pointer ; ; Register Usage (during call): ; A0.L - scratch D0.L - scratch ; A1.L - scratch D1.L - scratch ; A2.L - scratch D2.L - scratch ; A3.L - pointer to CatSearch State Record (CSSR) D3.L - scratch ; A4.L - pointer to PScan State Record (PSR) D4.L - scratch ; A5.L - pointer to user's param block D5.L - ioSearchBits ; A6.L - standard file system stack ; D7.L - ioActMatchCount ; ; Output: D0.W - result code (noErr, extFSErr, ioErr, nsvErr, catChanged, paramErr) ; A0.L, A1.L - trash ; D0.L, D1.L, D2.L - trash ;__________________________________________________________________________________ CMCatSearch: proc export jsrRom FSQUEUE ; queue up the request SUBA.W #CSSR.size, A6 ; allocate a CSSR on A6 moved here <1.3> CLR.B CSSR.flags(A6) ; clear the flags moved here <1.3> MOVEA.L A6, A3 ; move CSSR ptr to a safe register <12> MOVEA.L A0, A5 ; move userPB ptr to a safe register <12> move.l ioSearchTime(a5), d0 ; get max search time (time mgr count) <12> bsr StartTimer ; start a timer, if needed, right away <12> jsrRom DTRMV3 ; find vol using ioNamePtr & ioVRefNum (D023/A234 trashed) MOVEA.L A6, A3 ; reload pointer to CSSR (doesnÕt hurt CCs) <12> BNE @Exit ; (DtrmV3 puts VCBPtr in A2) MOVE.W vcbSigWord(A2), D0 ; Check out the volume signature CMP.W #SigWord, D0 ; Is this an MFS disk? ¥¥ use TFSVCBCk BNE.S @ItsHFS ; Keep going if not MOVE.W #wrgVolTypErr, D0 ; Tell the silly caller BRA @Exit ; and cruise @ItsHFS: jsrRom EXTOFFLINCK ; Is this volume on-line and one of ours? <1.2> BEQ.S @VolumeIsCool ; Keep going if so <1.2> BRA @Exit ; Go home if not (with extFSErr or volOffLinErr in D0) <1.2> @VolumeIsCool: MOVE.L ioSearchBits(A5), D5 ; D5 = user's spec mask MOVEQ.L #0, D7 ; no matches yet MOVE.L D7, ioActMatchCount(A5) ; set PB to initial value MOVE.L ioMatchPtr(A5), CSSR.nextMBuf(A3) ; 1st MBuf entry is at the beginning of the buffer ; a0 = cspb bsr VerifyCSPB ; d2 = ioFlAttrib, d3 = cssr.flags bne @Exit ; no? -> move.b d2, cssr.attribMask(a3) ; save copy of user's attrib bits or.b d3, cssr.flags(a3) ; add flags, keeping timer bit <13> ; look for conditions that don't return paramErr, but are reason to quit now TST.L ioReqMatchCount(A5) ; max 0 matches? BEQ @LeaveEarly ; just quit now; no error ; name setup - if the search includes any of the file name bits we need to make an ; uppercase copy of the target file name. BTST.L #inclNames, d3 ; does the search include names? BEQ.S @go ; if not, we're ready to roll MOVEA.L ioSearchInfo1(A5), A0 ; A0 = ptr(ioSearchInfo1) MOVEA.L ioFileName(A0), A0 ; A0 = ptr(ioName) MOVEQ.L #0, D0 ; clear D0 to clear high bytes MOVE.B (A0), D0 ; D0 = len(target cName) CMPI.B #31, D0 ; is length > max hfs length? <14> BHI.S @NoWayName ; then we do this little optimization <14> ADDQ.B #1, D0 ; D0 = len(target cName) + length byte LEA.L CSSR.copyTarg(A3), A1 ; A1 = ptr(copy of target cName) _BlockMove ; make the copy MOVEQ.L #0, D0 ; clear D0 to clear high bytes MOVEA.L A1, A0 ; A0 = ptr(length byte of copy of target cName) MOVE.B (A0)+, D0 ; D0 = len(target cName) _FSUprString ; uppercase, strip diacriticals bra.s @go ; <14> ; <14> ; Since we can't copy extra-long names into our private buffer (to let them fail against ; all the names on this Str31'd hfs volume) we'll psych out the parameter right here. @NoWayName: moveq.l #eofErr, d0 ; in case we need this btst.l #fsSBNegate, D5 ; do we match everything or nothing? beq.s @Exit ; if no negation, match nothing moveq.l #0, D5 ; if negation, clear criteria so as to match everything ; the parameters are cool, so start searching @go: MOVE.W vcbCTRef(A2), D0 ; catalog btree refnum BSR GetReadBuffer ; A1 = ptr(buffer), D3 = length, cssr.bufferOnStack set LEA.L ioCatPosition(A5), A0 ; A0 = ptr(CatPosition record in param block) BSR BTIPScan ; A4 is now ptr(PScan state record), 1st record is read BNE.S @EndScan ; BTIPScan returns a regular OSErr MOVE.L A4, CSSR.PSRPtr(A3) ; save a copy of the PScan state record @loop: BSR CheckCriteria ; do we have a match? BNE.S @continue ; Z=1 indicates a hit BSR MBufInsert ; yes, so add entry to match buffer ADDQ.L #1, D7 ; tally this match MOVE.L D7, ioActMatchCount(A5) ; update PB, so async users can watch CMP.L ioReqMatchCount(A5), D7 ; hit user match limit? BHS.S @LimitExit @continue: btst #timerFired, CSSR.flags(a3) ; did the timer fire? <12> bz.s @noTimerYet ; no, donÕt bug Mr. Scanner <12> bset #noMoreReads, PSR.flags(a4) ; yes, tell him not to hit the disk again <12> @noTimerYet BSR BTGetPhys ; on to the next record BNE.S @EndScan ; either a real error, or just EOF BRA.S @loop ; Exit code ; There are 5 paths out of CatSearch ; 1) LeaveEarly - set result to noErr, don't clean up PScan ; 2) ParamErrExit - set result to paramErr, don't clean up PScan ; 3) LimitExit - set result to noErr, and clean up the PScan ; 4) EndScan - don't touch the result, and clean up the PScan ; 5) Exit - don't touch the result code, don't clean up the PScan. @LeaveEarly: MOVEQ.L #noErr, D0 ; No error BRA.S @Exit ; No PScan cleanup @ParamErrExit: MOVE.W #paramErr, D0 BRA.S @Exit @LimitExit: MOVEQ.L #noErr, D0 ; user limit condition implies noErr @EndScan: CMP.W #userCanceledErr, d0 ; is this because noMoreReads was set? BNE.S @errorIsReal ; if not, this must be a real error MOVEQ.L #noErr, d0 ; if so, replace the signal error with noErr @errorIsReal: LEA.L ioCatPosition(A5), A0 ; A0 = ptr(CatPosition record in param block) BSR BTEndPScan ; clean up PScan (update CatPosition record) @Exit: ; Pass the error code on and exit BSR StopTimer ; Stop the timer if we started one <12> BTST.B #bufferOnStack, CSSR.flags(A3) ; do we have our buffer on the stack? BEQ.S @notBufferOnStack ; if not, don't deallocate one ADDA.L #CSMinBufferSize, A6 ; deallocate read buffer @notBufferOnStack: ADDA.W #CSSR.size, A6 ; deallocate CSSR suba.l a3, a3 ; clear WDCB pointer for external file systems <17> jmpRom CMDDONE _CSDebugRts 'CMCatSearch', 0 endproc end