sys7.1-doc-wip/OS/HFS/Extensions/ExternalMakeFSSpec.a

680 lines
24 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;
; Hacks to match MacOS (most recent first):
;
; <Sys7.1> 8/3/92 Reverted <SM3> by changing pascalRegs back to interruptRegs (i.e. not
; saving a2/a3/d3).
; 9/2/94 SuperMario ROM source dump (header preserved below)
;
;
; File: ExternalMakeFSSpec.a
;
; Contains: Code to perform emulation of the MakeFSSpec call
; for external file systems that don't support it
; themselves.
;
; This file can be assembled into code that relies on an a6 stack
; and QMgr.a-style queueing or into a simple subroutine for glue
; libraries. -d MakeGlue=0 should be used in system builds and
; -d MakeGlue=1 for glue library builds.
;
; Written by: Dave Feldman
;
; Copyright: © 1990-1991, 1993 by Apple Computer, Inc., all rights reserved.
;
; Change History (most recent first):
;
; <SM3> 6/9/93 pdw Changed register saving from interruptRegs to pascalRegs in
; myCompletionRoutine because it can be called from pascal (as it
; is when FileShare is running). Fixes a FileShare crash that
; appears when doing async I/O.
; <4> 9/13/91 JSM Cleanup header.
; <3> 3/17/91 dnf ppd, #dnf-0013: Use the result code from ioResult(a0) instead of
; from d0 to be extra-clean on async calls.
; <2> 9/22/90 dnf Put real code in here
; <1> 8/7/90 dnf first checked in
;
;
LOAD 'StandardEqu.d'
include 'FileMgrPrivate.a'
; Get inspired and move these somewhere
fsSpecNameLength equ 63
pathSeparator equ ':' ; a colon character
; Values for d3.b during parsing
moreSegments equ 0 ; also implies a colon-terminated segment
finalSegmentColon equ 1 ; no more segments; path ended with a colon
finalSegmentNoColon equ -1 ; no more segments; path didn't end with a colon
; For patch or ROM builds we need to include an i/o bottleneck to handle async calls.
; The bottleneck code assumes QMgr.a-style queueing.
if not(MakeGlue) then
include 'QMgrEqu.a'
;________________________________________________________________________________
;
; Routine: myBottleNeckIO
;
; Input: a0 points to param block
; Output: d0 contains result code
; a1 trash
;
; Function: Make traps which do I/O and do the right thing with completion
; routines.
;________________________________________________________________________________
;
BottleNeckRegs reg d1-d7/a1-a5 ; async could trash any regs
BottleNeckLocals record 0, increment
hasContinued ds.b 1 ; flag byte to watch stack depth
align 2
Lsize equ *-BottleNeckLocals
endr
myBottleNeckIO proc
import GetQMRecPtr
entry myCompletionRoutine
export myContCompatThread ; should be entry, but patch linker is dumb
export myContAppThread ; should be entry, but patch linker is dumb
movem.l BottleNeckRegs, -(a6) ; save our regs on hfs stack
with BottleNeckLocals
subq.w #Lsize, a6
bclr.b #0, hasContinued(a6) ; initialize completion routine flag
lea.l myCompletionRoutine, a1 ; get our ioCompletion routine address
move.l a1, ioCompletion(a0) ; and install it into the param block
move.w #fsCompatQType, d2 ; our queue type/refnum
bsr GetQMRecPtr ; a1 = ptr(QMRec)
move.l a6, QMRec.curStack(a1) ; save current alt stack pointer
; •• do high water mark checking here
btst.b #asyncCall, QMRec.qmFlags(a1) ; is this call async?
rts ; return to specific trap handling routine
; Some drivers have the nasty habit of calling their completion routines before returning
; from the trap that called them. This might lead to unbounded stack buildup from
; successive calls. To solve this we have a bit (in hasContinued) which is set both
; in the completion routine and after the trap.
; Case 1: The completion routine is called before we return from the trap. In this case
; we just cruise back to the driver, since we'll get control again when the driver
; rts's from our trap. When that happens we can just continue our call.
; Case 2: We get returned to before the completion routine is run (truly asynchronous).
; We rts to the app (giving back the async time) and we know we'll get control again
; at the completion routine. When we continue from here we need to save the
; appropriate interrupt registers.
; This little scheme is, of course, a critical section, but we're single threaded right now,
; so it doesn't matter.
myCompletionRoutine:
move.w #fsCompatQType, d2 ; our queue type/refnum
bsr GetQMRecPtr ; a1 = ptr(QMRec)
move.l a6, -(sp) ; save a6 for a sec
movea.l QMRec.curStack(a1), a6 ; get our a6 back
bset.b #0, hasContinued(a6) ; mark that we've been back
movea.l (sp)+, a6 ; restore a6
beq.s anRTSInstruction ; if we haven't returned from the trap, rts to driver
movem.l interruptRegs, -(sp) ; save all regs that pascal callers need saved <LW2>pdw <Sys7.1>
pea restoreInterruptRegs ; and get in the chain to restore them later <LW2>pdw <Sys7.1>
myContCompatThread:
move.w #fsCompatQType, d2 ; our queue type/refnum
bsr GetQMRecPtr ; a1 = ptr(QMRec)
movea.l QMRec.curStack(a1), a6 ; restore alt stack pointer
adda.w #Lsize, a6 ; clear off the locals
movem.l (a6)+, BottleNeckRegs ; restore compatibility layer thread registers
move.l (a6)+, -(sp) ; push compatibility layer thread return address
move.w ioResult(a0),d0 ; set the ol' status register <3>
anRTSInstruction:
rts ; and return to caller
myContAppThread:
; •• we're ignoring the possibility of immediate errors
bset.b #0, hasContinued(a6) ; mark that we've returned
bne.s myContCompatThread ; if we already ran the completion routine, then
; continue without saving registers
rts ; return async time to application
restoreInterruptRegs: ; <LW2>pdw <Sys7.1>
movem.l (sp)+, interruptRegs ; restore the regs that we saved last time through <Sys7.1>
rts ; back to the app thread
endproc
;________________________________________________________________________________
;
; Routine: DoAnA060Trap
;
; Input: a0 - paramblock
; d0 - HFSDispatch selector
;
; Output: a0 - paramblock
; d0 - result code
;
; Function: Fire off an a060 call
;
; Note: After myBottleNeckIO, Z = 1 -> async, Z = 0 -> sync.
;________________________________________________________________________________
DoAnA060Trap: proc
move.l (sp)+, -(a6) ; save thread ret addr on a6
bsr myBottleNeckIO ; do common set up
bne.s @1 ; zero flag set if async
_HFSDispatch ; sync trap
jmp myContCompatThread; keep going with this call
@1: _HFSDispatch async ; async trap
jmp myContAppThread ; keep going, but give async time to app
endproc
; Macro for generating code that executes a synchronous or asynchronous trap
; based on the value of the zero flag. Zero set = async, zero clear = sync.
macro
doSomeTrap &theTrap
move.l (sp)+, -(a6) ; save desktop thread ret addr on a6
bsr myBottleNeckIO ; do common set up
bne.s @1 ; zero flag set if async
_&theTrap ; sync trap
jmp myContCompatThread; keep going with this desktop call
@1: _&theTrap async ; async trap
jmp myContAppThread ; keep going, but give async time to app
endm
doGetCatInfo: proc
move.w #selectGetCatInfo, d0
jmp DoAnA060Trap
endproc
doGetWDInfo: proc
move.w #selectGetWDInfo, d0
jmp DoAnA060Trap
endproc
doGetVolInfo: proc
doSomeTrap GetVolInfo
endproc
doHGetVol: proc
doSomeTrap HGetVol
endproc
macro
go_GetCatInfo
bsr doGetCatInfo
endm
macro
go_GetWDInfo
bsr doGetWDInfo
endm
macro
go_GetVolInfo
bsr doGetVolInfo
endm
macro
go_HGetVol
bsr doHGetVol
endm
; For glue we can go straight to traps
else
macro
go_GetCatInfo
_GetCatInfo
endm
macro
go_GetWDInfo
_GetWDInfo
endm
macro
go_GetVolInfo
_GetVolInfo
endm
macro
go_HGetVol
_HGetVol
endm
endif
;________________________________________________________________________________
;
; Routine: ResetPathnameParser
;
; Input: a4 - caller's param block (uses ioNamePtr)
;
; Output: d1.l - number of characters in string (could be zero)
; a1 - pointer to first character in string (or nil)
;
; Function: Set registers used by parser to cause it to start at the beginning
; of the string when it is next called.
;________________________________________________________________________________
ResetPathnameParser: proc
moveq.l #0, d1 ; clear high bytes
move.l ioNamePtr(a4), d0 ; grab pathname pointer and set ccr's
movea.l d0, a1 ; move to a1 even if it's nil
beq.s @Exit
move.b (a1)+, d1 ; get the length and point at 1st char
@Exit:
rts
endproc
;________________________________________________________________________________
;
; Routine: ParseNextSegment
;
; Input: a1 - current location along pathname string
; a2 - pointer to FSSpec under construction
;
; d1.w - characters remaining in string in front of a1
;
; Output: a1 - update to new position along pathname string
; a2 - pointer to FSSpec under construction
;
; d0.w - error code (negative)
; d1.w - characters remaining in string in front of a1
;
; d3.l - stuff
; 0 moreSegments implies a colon-terminated segment
; 1 finalSegmentColon no more segments; path ended with a colon
; -1 finalSegmentNoColon no more segments; path didn't end with a colon
;
; Z
; set - saw an empty segment
; clear - saw a non-empty segment or an error occurred
;
; N
; set - error code in d0
; clear - success
;
; Function:
; Parse through the characters at (a1) and copy the
; next leaf (i.e. file/directory) name into FSSpec.name.
;
; Use the Z flag to indicate the presense of a '::' in the path
;
; Input:
; if d1 > 0 then
; a1 must point to the first character in the new segment
;
;________________________________________________________________________________
; ParseNextSegmentRegs reg a0
ParseNextSegment: proc
move.l a0, -(sp) ; save one register
move.w d2, -(sp) ; only low word
moveq.l #0, d0 ; no chars in this segment yet
lea.l FSSpec.name+1(a2), a0 ; address of 1st char in leaf name buffer
subq.b #1, d1 ; subtract one for dbra
cmp.b #pathSeparator, (a1) ; is this a colon?
beq.s @DoubleColon ; that means there's been 2 in a row
@Loop:
move.b (a1)+, d2 ; get the next character
cmp.b #pathSeparator, d2 ; is this a colon?
beq.s @SingleColon ; if so, we've got a whole segment
addq.b #1, d0 ; count the character
cmp.b #fsSpecNameLength, d0 ; are we overflowing FSSpec.name?
bhs.s @Overflow ; we overflowed, run for our lives...
move.b d2, (a0)+ ; copy the char into leaf buffer
dbra d1, @Loop ; while more characters keep going
moveq.l #finalSegmentNoColon, d3 ; we fell off the end, implying no colon there
bra.s @Success
@DoubleColon:
addq.l #1, a1 ; skip past the colon
@SingleColon:
moveq.l #moreSegments, d3 ; pretend we have more
tst.w d1 ; was the colon also the last character?
bne.s @Success ; if not, keep going
moveq.l #finalSegmentColon, d3 ; indicate our fate (done, with a colon)
@Success:
move.b d0, FSSpec.name(a2) ; turn FSSpec.name into a PString
@Exit:
move.w (sp)+, d2 ; restore low word
movea.l (sp)+, a0 ; restore one register
tst.w d0 ; Z if empty string, N if error
rts
@Overflow:
moveq.l #bdNamErr, d0 ; bummer - too many characters in file name
bra.s @Exit
endproc
;________________________________________________________________________________
;
; Routine: DetermineVolume
;
; Input:
; a1 - beginning of pathname (or nil)
; a2 - pointer to FSSpec under construction
; a4 - caller's param block (uses ioVRefNum and ioDirID)
;
; d1 - # of characters in string (could be zero)
; Output:
; FSSpec.vRefNum contains volume reference number
;
; d1.w - number of characters left in pathname
; d4.l - dirID of selected directory
; a1 - place to start parsing pathname from
; a2 - pointer to FSSpec under construction
; a4 - caller's param block (uses ioVRefNum and ioDirID)
;
; Function: Parse up ioVRefnum, ioNamePtr and ioDir to find the volume/directory
; pair. Set the FSSpec to describe the vol/dir pair, and return the
; dirID of the directory in question in d4.
;
; Handle all of the no-path-to-parse cases here:
; no vRef, dirID or path
; just a vRef
; just a wdRef
; just a vRef+dirID
; a colon for a pathname
;
;________________________________________________________________________________
DetermineVolume: proc
@DetermineVolumeRegs: reg d2/a0/a3
movem.l @DetermineVolumeRegs, -(a6)
suba.w #ioHVQElSize, a6 ; allocate a volume param block on a6
move.l a6, a0 ; leave pb in a0
; Check to see if there is a non-nil, non-zero length pathname
moveq.l #finalSegmentColon, d3 ; assume we have nothing to parse
tst.b d1 ; do we have a nil ptr or zero length name?
beq.s @UseVRefAndDirID ; if so, go use vRef+dirID
bsr ParseNextSegment ; parse next segment into FSSpec.name
bmi.s @Exit ; can't party if the parser don't want to
beq.s @UseVRefAndDirID ; initial colon implies vRef+dirID
tst.l d3 ; check parser results
bpl.s @VolumeNamePresent ; if it ended with a colon, it's a vol name
; We've got a special case - the pathname is a solo cname. Reset the path parser
; so that we'll see it again.
bsr ResetPathnameParser
moveq.l #moreSegments, d3 ; make sure they look at this again
bra.s @UseVRefAndDirID ; and go use the vRef and dirID fields
@VolumeNamePresent:
clr.w ioVRefNum(a0) ; use pathname, please
move.w #-1, ioFDirIndex(a0) ; use pathname, please
lea.l FSSpec.name(a2), a3 ; get the address of the just-parsed segment
move.l a3, ioNamePtr(a0) ; use caller's pathname
; slam a colon on because HFS sucks rocks
addq.b #1,d0 ; add a colon on to the end
cmp.b #fsSpecNameLength,d0 ; ooops! no space
bhs.s @BadNameError
move.b d0, (a3) ; jam the longer length
move.b #pathSeparator, (a3, d0.w) ; put in the lovely separator for the pleasure of Dirks
go_GetVolInfo
bne.s @Exit ; run away
move.w ioVRefNum(a0), FSSpec.vRefNum(a2) ; this is the volume
move.l #fsRtDirID, d4 ; use the root if we need to keep going
bra.s @Exit ; and we're done
; We don't have a volume name ('cause the path starts with a colon, the path is a
; solo file/directory name, or there's no path), so use the ioVRefNum and ioDirID
; fields to establish the volume and directory to start parsing from.
@UseVRefAndDirID:
clr.l ioNamePtr(a0) ; we don't need the name here
move.w ioVRefNum(a4), ioVRefNum(a0) ; use caller's vRef/WDRef
beq.s @HandleDefaultCase ; GetWDInfo has no clue about default directories
clr.w ioWDIndex(a0) ; no indexing, please
clr.l ioWDProcID(a0) ; any proc ID will do
clr.w ioWDVRefNum(a0) ; any volume will do
go_GetWDInfo
bne.s @Exit ; punt on errors
bra.s @GotVolAndDir
@HandleDefaultCase:
go_HGetVol
bne.s @Exit
@GotVolAndDir:
move.w ioWDVRefNum(a0), FSSpec.vRefNum(a2) ; use the volume from GetWDInfo
move.l ioDirID(a4), d4 ; are we overriding with the user's dirID?
bne.s @Exit ; if so, stop thinking now
move.l ioWDDirID(a0), d4 ; use dirID from vRef/wdRef or pathname
@Exit:
adda.w #ioHVQElSize, a6 ; deallocate volume param block on a6
movem.l (a6)+, @DetermineVolumeRegs
tst.w d0
rts
@BadNameError:
moveq.l #bdNamErr, d0 ; bummer - too many characters in file name
bra.s @Exit
endproc
;________________________________________________________________________________
;
; Routine: CorrectCapitals
;
; Input: a2 - pointer to FSSpec with leaf name
;
; Output: a2 - FSSpec has correct (same-as-catalog) spelling of leaf name
; d0 - result code
;
; Function: Replace the leaf name in FSSpec.name(a2) with the spelling as it
; is stored in the catalog.
;________________________________________________________________________________
CorrectCapitalsRegs reg a0/a1
CorrectCapitals proc
move.l (sp)+, -(a6) ; ret addr to local stack
movem.l CorrectCapitalsRegs, -(a6)
suba.w #ioHVQElSize, a6 ; allocate a pb
movea.l a6, a0
suba.w #256, a6 ; max name length is 256 bytes
movea.l a6, a1 ; a1 = ptr(name buffer)
move.l a1, ioFileName(a0) ; provide space for name output
move.w FSSpec.vRefNum(a2), ioVRefNum(a0) ; use the right volume
move.l FSSpec.parID(a2), d0 ; get parent directory
cmp.l #fsRtParID, d0 ; are we looking for the root's name?
beq.s @RootName
move.w #1, ioFDirIndex(a0) ; start indexing at 1
move.l d0, ioDirID(a0) ; use the right directory
@IndexLoop:
go_GetCatInfo
bne.s @Exit ; punt on errors
move.l a0,-(sp) ; stash regs
move.l a1,-(sp)
lea FSSpec.name(a2), a0 ; point to source name
moveq.l #0, d0 ; clear everything
move.b (a0)+, d0 ; length of caller's string
swap d0 ; up high where _RelString likes it
move.b (a1)+, d0 ; length of catalog's string
_FSRelString ; compare 'em
movea.l (sp)+, a1 ; restore regs
movea.l (sp)+, a0
beq.s @Hit ; cruise when they match
add.w #1, ioFDirIndex(a0) ; check out the next one
move.l FSSpec.parID(a2), ioDirID(a0) ; use the right directory
bra.s @IndexLoop
; GetCatInfo seems to not want to return info about the root directory when asked for
; the first item (indexed) in fsRtParID. Thus, we hack.
@RootName:
clr.w ioVolIndex(a0) ; no indexing for us
go_GetVolInfo
bne.s @Exit
@Hit:
moveq.l #0, d0 ; clear everything
lea.l FSSpec.name(a2),a0 ; a0 target FSSpec
move.b (a1), d0 ; get length of catalog-spelled name
cmp.b #63, d0 ; a massive MFS name?
bls.s @1 ; if length <= 63, we've enough room
moveq.l #63, d0 ; sorry; you only get the 1st 63 chars
@1:
move.b d0, (a1) ; set clipped string length back into source
@loop:
move.b (a1)+, (a0)+ ; set chars into FSSpec.name
dbra d0, @loop
moveq.l #noErr, d0 ; yay!
@Exit:
adda.w #ioHVQElSize+256,a6 ; deallocate the pb and name buffer
movem.l (a6)+, CorrectCapitalsRegs
move.l (a6)+, -(sp) ; ret addr back
tst.w d0
rts
endproc
;________________________________________________________________________________
;
; Routine: ExternalMakeFSSpec
;
; Input: a0 - caller's param block (uses ioVRefNum, ioNamePtr, ioDirID and ioMisc)
;
; Output: FSSpec (pointed to by ioMisc) is properly set
; d0 - result code
;
; Function: Parse up ioVRefnum, ioNamePtr and ioDir to make an FSSpec
;
; register usage:
; a0 - our working parameter block
; a1 - current place along the caller's pathname
; a2 - FSSpec record
; a4 - caller's param block
; d1 - characters left in pathname in front of a1
;
; Assumes that suitably big a6 (≈800 bytes) is set up (presumably the compatibility layer)
; This routine follows all of the conventions to be called in the compatibility layer
; but would also run fine as glue if someone aims a6 at a stack.
;________________________________________________________________________________
ExternalMakeFSSpecRegs reg a2-a4/d3-d4
ExternalMakeFSSpec: proc export
move.l (sp)+, -(a6) ; we do i/o
movem.l ExternalMakeFSSpecRegs, -(a6)
movea.l a0, a4 ; save caller's pb pointer in a safe reg
sub.w #ioHVQElSize, a6 ; allocate a (big) param block on a6
movea.l a6, a0 ; keep a pointer to it around in a0
move.l ioFSSpecPtr(a4), a2 ; a2 = ptr(Caller's FSSpec record)
lea.l FSSpec.name(a2), a3 ; use FSSpec.name for path segment name storage
move.l a3, ioNamePtr(a0) ; have
bsr ResetPathnameParser ; get ready to parse pathname
bsr DetermineVolume ; go get the volume and directory established
bne @ErrorExit
move.w FSSpec.vRefNum(a2), ioVRefNum(a0) ; we know the volume now
@DirIDButNoCatInfo:
move.l d4, ioDirID(a0) ; remember this so we can recognize our parents
tst.l d3 ; check result of last parse
bne.s @GetCatInfoByDirID ; no more segments? Get the name and par of this dir
@ParseSegments:
bsr ParseNextSegment ; using a1/d1 return next segment
bmi @ErrorExit ; splitsville if the parser won't dance
beq.s @DoubleColon ; handle double colon case
clr.w ioFDirIndex(a0) ; use dirID and name, please
go_GetCatInfo ; look and see if we can find anything
; There are two reasons that the following code rejects files when a colon is present:
; 1) a file in the middle of a path is an error, and its ioDirID is not a dirID
; 2) a file at the end of a colon-terminated path is an error, too (Bill B. sez)
bne.s @WeLikeItFine ; an error happened, so we cant be picky
btst.b #ioDirFlg, ioFlAttrib(a0) ; was the thing we found a directory?
bne.s @WeLikeItFine ; yes, and were always happy to find directories
tst.l d3 ; did we have a colon?
bmi.s @WeLikeItFine ; no, and that is correct for a file
moveq.l #dirNFErr, d0 ; silly rabbi, colons are for directories
bra.s @ErrorExit
@WeLikeItFine:
tst.l d3 ; are we out of segments?
bne.s @DoneParsing ; if so, got to get out
tst.w d0 ; did we have trouble getting through the directory
bne.s @ErrorExit ; errors are our cue to cruise
move.l ioDirID(a0), d4 ; now, look within this directory
bra.s @ParseSegments
@DoubleColon:
cmp.l #fsRtDirID, d4 ; are we trying to go higher than the root?
beq.s @DirNFExit ; yes, we cant (even though GetCatInfo would)
move.w #-1, ioFDirIndex(a0) ; we need this guy's parent
go_GetCatInfo
bne.s @ErrorExit ; barf on badness
move.l ioFlParID(a0), d4 ; get parent because we are done
bra.s @DirIDButNoCatInfo
@GetCatInfoByDirID:
move.w #-1, ioFDirIndex(a0) ; we need this guy's parent
go_GetCatInfo
bne.s @ErrorExit ; barf on badness
move.l ioFlParID(a0), d4 ; get parent because we are done
@DoneParsing:
move.l d4, FSSpec.parID(a2) ; use the parent from the last GetCatInfo
tst.w d0 ; did we get an error here?
beq.s @FoundSomething ; if not, we found something real
cmp.w #fnfErr, d0 ; did we get fnfErr, a perfectly acceptable error?
bne.s @ErrorExit ; no, it was another error, report it
; GetCatInfo returns an fnfErr instead of a dirNFErr when given a bad directory ID. Since an fnfErr
; from MakeFSSpec is an invitation to call _Create, we filter out bad directories here.
move.w #-1, ioFDirIndex(a0) ; info about directory
clr.l ioNamePtr(a0) ; we don't want the name back
move.l FSSpec.parID(a2), ioDirID(a0) ; use the directory we're about to return
go_GetCatInfo
bne.s @ErrorExit ; an error implies a bad directory
moveq.l #fnfErr, d0 ; good directory, so restore the fnfErr
cmp.l #fsRtParID, d4 ; were we searching in the roots parent?
bne.s @Exit ; no, we can return safely (can't create in fsRtParID)
@ErrorExit:
cmp.w #fnfErr, d0 ; if an fnfErr made it here, it is really a dirNFErr
bne.s @notFNF
@DirNFExit:
moveq.l #dirNFErr, d0 ; return a dirNFErr instead
@notFNF:
clr.w (a2)+ ; clear the FSSpec.vRefNum
clr.l (a2)+ ; clear the FSSpec.parID
clr.b (a2)+ ; clear the FSSpec.name length byte
bra.s @Exit
@FoundSomething:
bsr CorrectCapitals ; Get correct spelling of leaf name
@Exit:
adda.w #ioHVQElSize, a6 ; deallocate param block
movem.l (a6)+, ExternalMakeFSSpecRegs
move.l (a6)+, -(sp)
tst.w d0
rts
endproc
end