        TTL     > <wini>arm.FileSwitch.FileSwBody : FileSwitch - Top layer of Filing System rubbish

; 1.20 is major rework for reentrancy. Released to Sam for new 1.21+ OS's
; 1.21 internal
; 1.22 GBPB write ordering rehack
; 1.23 Block freeing reorder, transient freeing fixed
; 1.2401 GBPB 9/10/11 fixes; Count/Wipe/Copy allow longer names. Less stack use
;     02 Read address of EnvString/EnvTime to set command line up
;     03 Fix totalsize addition in CopyDirectory errors
;     04 Debungling version to suss grope fulkup
;     05 Removed FED (Palette) types because of inconsistent use
;     06 Debungling version to suss aasm fulkup
; 1.2501 Some OSArgs bugs fixed, Unbuffered filing systems work again
;     02 Error buginess removed, BGet fixed
;     03 Debungling for close problem
;     04 Copy fixed wrt. restamping
;     05 Changed error text of copying onto dir to make sense
;     06 Debugging version, bad file names returned in error strings
;     07 Template and BBC ROM types added
;     08 Palette actions put back in again
; 1.2601 Alias$@xxxType reinstated
;     02 Debugging for transient run problems
;     03 Transient sp bug fixed
; 1.2701 foo.!Run stuff added. Debungling messages in LowLevel fixed
;     02 Error overflow better handled ...
;     03 Added FileType_xxx variables
;     04 Added FileType_xxx variable handling
;     05 Debug for copy/wipe exception
;     06 FSUtils revamped/fixed. StructureOnly copy option added. Code
;        shorter, stack frame smaller, global area smaller
; 1.2801 Added -newer option to copy
;     02 Upcalls for osfile/osfind added
;     03 SetType takes names for file types
;     04 Pervy calling mechanism when in ROM now !
;     05 Changed LCat to not give spurious BadFileName error. Changed help
;     06 Fixed -newer option. New FileTypeFromString op
;     07 Fixed *count st. only files counted
;     08 Refixed *count st. it printed again !
; 1.2901 Added BPut cache
;     02 Added WriteCAO call to get proper divorce from OS
;     03 Cache debugging again
;     04 Filename debugging for C
;     05 _FED set to WimpPalette
; 1.3001 Added flush buffers call for Nick to ensure his buffers to disc
;        Gave to Sam for 1.34? MOS
; 1.3101 Fixed copy -newer to use unsigned CMP (works better)
;     02 Debugging osfind
;     03 Append handle works now for fuckups in Read/WriteMultiple
; 1.3201 Changed ReadActionVariable to produce 1.20 compatible aliases again
;        Also loops over numeric variables to see if variable does exist
;     02 Fixed !Run stuff in ExPathCommon and handle leak in *copy. OS 1.35
; 1.3301 Fixed *copy t option to force r as well
;     02 *copy now saner wrt. 'not copied' and skipping with reason
;     03 *wipe/*count also saner; doesn't say 'and is locked' after datecmpfail
;     04 *copy syntax relaxed. ReadTime shorter. Commoned up more utils code
;     05 Fixed major fuckup with globalerror and errors going missing in
;        reentrant situations. *opt shorter. Speedup in FS entry code
;     06 Tried to fix OSGBPB unnecessary underlay reading
;     07 Added *FileInfo
;     08 Fixed vector problems
;     09 Fixed BPut/BGet cache causing write to UnallocatedStream
;     10 Fixed BPut/BGet stack corruption
;     11 Fixed BPut stack level
;     12 Fixed file restamping
;     13 Added WimpRun alias for FEB
;     14 Debugging for HCM GBPB bug
; 1.3401 Fixed HCM GBPB bug
;     02 Added WimpRun file type
;     03 Added 'Too big' error
;     04 Another GBPB bug fixed, added the other day. code slightly smaller
;        Also checks GBPB write to see if silly (bug 601)
;        WimpRun changed to Obey for new scheme of things. New MOS
; 1.3501 Obey changed to FEA; GBPB power of two inefficiency believed solved
;     02 Rename and Access now do UpCall
;     03 fp now restored in error cases before CopyError! fp set to 0 in commns
;     04 ReadFSName FSControl added; lots saved in FSControl by commoning up
;     05 Fixes in *copy syntax relaxation, move code shavings
;     06 Fixed GBPB write ordering for efficient write behind. Into MOS ...
;     07 Chase core corruption in *copy. Made better wrt. being in ROM
; 1.3601 Copy fixed; also has use abs buffer option for JThackray
;        Writes 'DEAD' into file load and exec while invalid during copy
;     02 Fulkup in SFreeCopy fixed; BPut cache moved to kernel
;     03 Obey changed to FEB again (bastard didn't tell me)
;     04 Debug for settype. Released for 1.60 MOS
; 1.3701 Fixed call to GetFileBuffer in OSGBPB ReadMultiple
;     02 SetType <number> fixed
;     03 Changes to help texts; uses tokenised forms now. Uses Immediate for
;        backwards addressing. New copy option (L)
;     04 Fixed GBPB write bug and inefficiency
;     05 Finally tracked Copy wierd behaviour down to routines getting VSet
;        from date comparisons ! Aaargh. Debug for deep wipes. Bugs in
;        file closing, block deallocation fixed in copy
;     06 Changed errors to not overflow in all cases. Upcall from close
; 1.3801 Changed heap allocation to try and extend system heap if claim fails
;     02 Fixed help strings wrt. Token0's
;     03 Seriously wacky new buffer allocation in progress. Caution !!!
;     04 Changed copy a * to use CopyWildObject not CopyObject
;     05 Fixed copy to do XOS_Find to close files rather than BL CloseFile
;        as globalerror being set gave totally confusing error messages
;        when the destination gave an error in creation and we closed src!
;     06 Debug for copy upcall; upcall fixed to recognise MediaNotKnown
;        and pass on in sensible order
; 1.3901 Copy upcall fixed to check upcall reason after it gets passed back
;     02 Help messages fixed again
;     03 And again
;     04 debug for SetExtent in ReadMail. False alarm - Twin had changed!
; 1.4001 New code for moving bytes around in and out of buffers
;        Tweaked prompt to say 'Quiet/Abandon'
;     02 Fixed minor bits in FSUtils user interfake, bug in PromptSpaceBar
;     03 Made OpenOut return scb_modifed always. Made Make,ClearCritical macros
;     04 If ROM FileSwitch got restarted with different workspace, it could
;        crap on 01800014 anyway if errors!
;     05 Changed string allocation to be kept locally, rather than fully
;        globally to allow dethreading of FileSwitch in any order.
;     06 0 accepted as '*' for wildcard match in OS_GBPB (so Logistix works)
;     07 fp used to get distinct upcall handlers. some bugs in string alloc
;     08 now only one instance of upcall handlers, with ref count, as could not
;        guarantee uniqueness between domains.
;     09 irq debugging help for Nick
;     10 permit leading spaces in filename expansion. Fixed wipe/count/rename
;     11 fixed copy (it had a bigger local frame and couldn't cope)
; 1.4101 fixed wipe/count (used non-local object)
;     02 fixed OSArgs for reentrancy stuffing; also faster if not directory
;     03 fixed wipe/count again (used non-local object). Allow longer paths
;     04 got rid of StaticName
; 1.4201 fixed MoveBytes
;     02 Fixed Copy restamp
; 1.4301 added SetContexts call
;     02 make local frame hang backwards off fp to access i/p registers easier
;     03 fixed bug of long standing; could have corrupted FIQ ws before
;     04 speeded up InvalidateCache
; 1.4401 make copy use wimp free slot. Shorten exit sequences. A few more addr
;        FileSystem help text fixed
;     02 debug for bget
; 1.4501 OSFile_MakeError
; 1.4601 Changed way copy treats sources of memory
; 1.4701 Start to put path stuff in
;     02 Better test for date stamp (CMN &00100000). Better RUN errors
;     03 debugging on FSControl for FileCore redeclaration
;     04 Take ValidateAddress out of GBPB so that copy works under wimp - it
;        also slows GBPB down far too much. Punter made responsible; sorry!
;     05 OSArgs/OSGBPB speeded up if rc in 00..FF, slower otherwise
;     06 Changed !Run implementation as it had rotted
; 1.4801 Changed back to ConvertCardinal for PrintFullSize. SpacedCardinal yuk
;     02 REEEEEELY nasty BPut speedup for kernel cache; deviant wp
; 1.4901 Conditionally removed AppendHandleToError
;     02 Fixed bugs introduced by above change
; 1.5001 Tweak messages for copy verbose
;     02 Separate data lost errors on flushes to foul correct stream
;        Made it behave better if you play silly buggers with task module
;        Made it do less disc swaps if P and L options set -> ~L always
;     03 Added type for PoScript
;     04 Fixed Rename errors for wildcards to be more consistent
;     05 Fixed Copy CDir not to look for object now Bruce has got it right
;     06 Didn't bother to do the above as we got more meaningful error
;        Make errors where parent dir of dest not a dir better
;        More concrete copy Q; bum - I can't do the above ^^^ cos of null: etc
;        Make errors in loading files during newapp go sprong
;
; -------------------       release for 1.85        ---------------------------
; 1.5101 Debug for copy Q
;     02 Approved changes for 1.86: all marked with >>>a186<<<
;        FSUtils: changed dummy userbuffer to &4000 so it doesn't conflict
;                  with apl claim/release.
;                 added another test on the util_look bit so that Copy using
;                  GBPB doesn't prompt twice
;                 removed spurious 3 instructions
;                 fixed CopyDirectory with respect to dest dir locking
;     03 More approved changes for 1.86: all marked with >>>a186<<<
;        OSGBPB:  fixed so exception conditions don't sprong and unbuffereds
;                  work again
;        FSControl: fixed so service comes AFTER UpCall again like it was
;     04 More approved changes for 1.86: all marked with >>>a186<<<
;        OSFile:   fixed so duff reason codes don't explode
;        FSCommon: fixed so SFreePathString doesn't corrupt caller's V
;     05 Unapproved changes for 1.86: all marked with >>>a186<<<
;        FSUtils: Way I was doing apl locking was wally and wrong
;                 also didn't get &8000 as buffer pointer in one path thru
;                 Fixed amusing bug when we get 0 bytes back from all sources!
; 1.5201 More approved changes for 1.86: all marked with >>>a186<<<
;        FSCtrl2: Fixed so gives bad file type error
;        FSUtils: Fixed so it does StartApplication in Copy Q
;        FSCommon:Tweaked to allow the above change
;     02 Now does domain stuff properly on alloc/free all
;     03 LowLevel: Now takes space as a terminator for OPT 1 string
;        FSCommon: tweaks to strxxx to allow the above
;     04 Approved changes for 1.86: all marked with >>>a186<<<
;        FSUtils: fix copy bug with ~F~C in GBPB case; take off one disc swap
;                 when source has expired, fix copy bug with stamping wrong
;                 file in copy p case
;     05 Unapproved changes for 1.86:
;        FSControl: set MOS handlers in StartApplication (see Sam)
;     06 FileSwBody: Alter number of args for Cat/LCat
;     07 FSUtils: Don't prompt ever if datecmp fails.
;     08 Debug for file corruption problem - was in RamFS drivers
; 1.5301 FSCtrl2: Fix for ConvertHandle
;     02 Debug for other file corruption problem
; 1.5401 StreamBits: Fixed FlushSomeone to remove buffer reference from
;                    flushed stream
; 1.55   FileSwBody: run type of text files := *Type

                GBLS    FileSwitchVerNo
FileSwitchVerNo SETS    "1.55"

                GBLS    FileSwitchMinor
 [ :LNOT: AssemblingArthur
FileSwitchMinor SETS    "/01"
 ]

        GET     &.Hdr.UpCall
        GET     &.Hdr.LowFSi
        GET     &.Hdr.Wimp              ; It happens to us all in the end ...


                GBLS    fileprefix      ; Default null source prefix
 [ AssemblingArthur
fileprefix      SETS    "FSCode."

                GBLL    anyfiledebug
anyfiledebug    SETL    False           ; Debug not permitted in the ROM
 ]

                GBLL    appendhandle
appendhandle    SETL    False

; ++++++++++++++++++++++++ Section debug switches +++++++++++++++++++++++++++++

        MACRO
$l      MakeDebug $t
        GBLL    $l
$l      SETL    anyfiledebug :LAND: ($t)
        MEND

debugosfile     MakeDebug       False
debugosfind     MakeDebug       True
debugstream     MakeDebug       True
debugosargs     MakeDebug       True
debugosbget     MakeDebug       False
debugosbgetcache MakeDebug      debugosbget :LAND: True
debugosbput     MakeDebug       False
debugosbputcache MakeDebug      debugosbput :LAND: True
debugosgbpb     MakeDebug       True
debugdaftgbpb   MakeDebug       False
debugcontrol    MakeDebug       False
debugrun        MakeDebug       False

debugcopy       MakeDebug       False
debugwipe       MakeDebug       False
debugcount      MakeDebug       False
debugutil       MakeDebug       debugcopy :LOR: debugwipe :LOR: debugcount

debugheap       MakeDebug       False
debugpath       MakeDebug       False
debugname       MakeDebug       False

debuglowfile    MakeDebug       False
debuglowfunc    MakeDebug       True
debuglowxfer    MakeDebug       True

paranoid        MakeDebug       False
debuginit       MakeDebug       False
debugservice    MakeDebug       False
debugvector     MakeDebug       False
debugerror      MakeDebug       False

debugosgbpbirq  MakeDebug       False
debugframe      MakeDebug       False

Proc_Debug      SETL anyfiledebug :LAND: True

 [ anyfiledebug
Host_Debug SETL False ;True
 ]

        GBLS    Host_Inclusion
Host_Inclusion SETS "; No Host debug wanted"
 [ anyfiledebug
  [ Host_Debug
Host_Inclusion SETS " GET &.Hdr.HostFS"
  ]
 ]
$Host_Inclusion

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Flags to strip out copy + wipe for MOS debugging versions

        GBLL    hascopy
        GBLL    haswipe
        GBLL    hascount
        GBLL    hasutil

 [ ChopOffTheGoolies ; For Sam MOS debugging versions
hascopy  SETL   False
haswipe  SETL   False
hascount SETL   False
 |
hascopy  SETL   True
haswipe  SETL   True
hascount SETL   True
 ]

hasutil SETL    hascopy :LOR: haswipe :LOR: hascount

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Other implementation modofiers

                GBLL    sparebuffer
sparebuffer     SETL    True

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Some FileSwitch specific macros

; Range check a register against (unsigned) register/immediate values
; CS if in [b1..b2], CC otherwise

        MACRO
$label  RangeCheck $reg, $wrk, $b1, $b2
$label  CMP     $reg, $b1
        RSBCSS  $wrk, $reg, $b2         ; inverse compare
        MEND

; For new Tutu style jump tables (4S + 2N cycles)

        MACRO
$label  JTAB    $dispreg, $cond, $name, $baseval
$label  ADD$cond pc, pc, $dispreg, LSL #2
JumpTableName SETS "$name.JumpTable"
$JumpTableName * .+4    ; Allow punter one instruction following JTAB condition
 [ "$baseval" = ""
JumpTableBaseValue SETA 0
 |
JumpTableBaseValue SETA $baseval
 ]
 [ ("$cond" = "") :LOR: ("$cond" = "AL")
        MOVNV   r0, r0
 ]
        MEND

        GBLS    JumpTableName
        GBLA    JumpTableBaseValue
        GBLL    ReportJumpTableError
ReportJumpTableError SETL True

        MACRO
        JTE     $routine, $checkvalue
 [ "$checkvalue" <> ""
  [ (.-$JumpTableName) <> ($checkvalue-JumpTableBaseValue) :SHL: 2
   [ ReportJumpTableError
        !       1, "Error in jump table with value '$checkvalue'"
ReportJumpTableError SETL False
   ]
  ]
 ]
        B       $routine
        MEND


; General initialisation for buffered file i/o

; In    scb^ valid
;       fileptr may be > extent if cached BPut, so correct if need to

; Out   status  = scb status bits
;       fileptr = PTR#
;       extent  = EXT# (corrected after effect of BPut cache)
;       bufmask = (size of file buffers - 1)
;       bcb     = first bcb or Nowt (optional)

        MACRO
$label  ReadStreamInfo $bcb
 assert :INDEX: scb_status = 0 ; For LDM
 assert fileptr > status
 assert scb_fileptr = scb_status + 4
 assert extent > fileptr
 assert scb_extent = scb_fileptr + 4
 assert bufmask > extent
 assert scb_bufmask = scb_extent + 4
 [ "$bcb" <> ""
 assert bcb > bufmask
 assert scb_bcb = scb_bufmask + 4
 assert $bcb = bcb
$label  LDMIA   scb, {status, fileptr, extent, bufmask, bcb}
 |
$label  LDMIA   scb, {status, fileptr, extent, bufmask}
 ]
        CMP     fileptr, extent
        MOVHI   extent, fileptr         ; If fileptr > extent, correct for
        STRHI   extent, scb_extent      ; having overshot (BPut cache effect)
 [ debugstream
        Push    lr
        BL      DebugStreamInfo
        Pull    lr
 ]
        MEND

addr_verbose    SETA    0

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

HSP     *       31
space   *       " "
quote   *       """"
solidus *       "|"
delete  *       &7F

Nowt    *       &40000000               ; Such that CMP ptr, #Nowt gives VClear
                                        ; &80000000 is a very BAD choice !

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Register allocation

; r0 generally used for data/reason codes
; r1 mainly used as handle for stream ops, filename/string^ for named ops
; r2 used for OSARGS data, GBPB memory address
status  RN      r3
fileptr RN      r4 ; Relied on by GBPB
extent  RN      r5 ; Relied on by GBPB
bufmask RN      r6
        GBLS    streaminfo
streaminfo SETS "r3-r6"
; r7 seems mostly unused
bcb     RN      r8  ; Buffer cb^
scb     RN      r9  ; Stream cb^
fscb    RN      r10 ; Filing System cb^
fp      RN      r11 ; Local workspace^
; wp RN r12 ; Global workspace^
; sp RN r13 ; FD SVC stack^
; lr RN r14

 assert fp + 1 = wp ; For LDMIA rn, {fp, wp} in LowLevel

; *****************************************************************************
; *** Enter a vectored SWI handler saving registers, fp but not lr (already ***
; *** assumed stacked eg. by the CallVector routine). Setup fp, clear error ***
; *** PullSwiEnv will leave lr stacked for exit                             ***
; *****************************************************************************

; +------------+
; |     lr     |
; +------------+ <- vector entry sp
; |     fp     |
; +------------+
; |            |
; -            -  } Stacked registers, accessed using [fp, #rn*4]
; |            |
; +------------+
; |    r(0)    |
; +------------+ <- fp
; |            |
; | Local vbls |  } Accessed using [fp, #-offset]
; |            |
; +------------+ <- fp - lfsz (also initial sp)

; $extra option for FSUtils, which need more local state

        MACRO
$label  NewSwiEntry $reglist,$extra,$savecode
 [ "$reglist" = ""
Proc_RegList    SETS "fp"
 |
Proc_RegList    SETS "$reglist, fp"
 ]
Proc_LocalStack SETA $extra.localframesize
 [ "$label" <> ""
  [ Proc_Debug
        B       $label
        DCB     "$label", 0
  ]
$label  ROUT
 |
 assert (.-Module_BaseAddr :AND: 3) = 0
 ]
        Push    "$Proc_RegList"
 [ "$savecode" = ""
        InitialiseFrame
 ]
        MEND


        MACRO
$label  InitialiseFrame
$label  TEQP    pc, #SVC_mode           ; Reenable interrupts
        MOV     fp, sp                  ; Point to dumped registers
        SUB     sp, sp, #Proc_LocalStack ; Never zero for main entry frame
 [ debugframe
 DREG fp,"InitialiseFrame: fp := "
 ]
        MOV     r14, #0                 ; No error this level
        STR     r14, globalerror        ; Don't poke into the frame until
        MEND                            ; we've created it !

; *****************************************************************************
; *** Restore stack and saved registers to values on entry to SWI handler   ***
; *** lr left stacked                                                       ***
; *****************************************************************************
        MACRO
$label  PullSwiEnv $cond
$label
 [ debugframe
 DREG fp,"Exiting frame: fp = "
 ]
 [ Proc_LocalStack <> 0
        ADD$cond sp, sp, #Proc_LocalStack
 ]
 [ "$Proc_RegList" <> ""
        Pull    "$Proc_RegList", $cond
 ]
        MEND


; Labels 99xx reserved for below purpose - ensure consistency

        MACRO
$label  SwiExit $cond
$label
 [ debugframe
 B$cond %FT9900
 B %FT9901
9900
 DREG fp,"SwiExit: fp = "
9901
 ]
        LDR$cond r14, globalerror       ; do this before frame destroyed
9999    PullSwiEnv $cond
        B$cond  FileSwitchExit
        MEND

 [ False ; Probably won't ever need this one again
        MACRO
$label  LethalSwiExit $cond
$label  PullSwiEnv $cond
        B$cond  LethalFileSwitchExit
        MEND
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Record for copy block

                ^       0
copy_name       #       4       ; Filename for copy (dirprefix + leafname)
copy_special    #       4       ; Special field for copy
copy_fscb       #       4       ; Filing System for copy
copy_dirprefix  #       4       ; Dir prefix to use
copy_leafname   #       4       ; Leafname to copy
copy_size       #       0

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; FileSwitch global area workspace definitions

                ^       0, wp ; Workspace accesses are wp register relative

BGet_shiftedbase # 4            ; BGet cache data must be at offset 0
BGet_bufferdata  # 4
BGet_scb         # 4
BGet_shift       # 4
BGet_handle      # 4

BPut_shiftedbase # 4
BPut_bufferdata  # 4
BPut_scb         # 4
BPut_shift       # 4
BPut_handle      # 4


errorbuffer     #       256     ; Buffer to build or copy error messages into


FileSwitch_FirstNowtVar # 0     ; First var to initialise to Nowt on hard reset

LinkedAreas     #       4       ; Points to chain of linked areas
 [ :INDEX: LinkedAreas <> &128
 ! 0, "Change ShowHeap linkedareas to &":CC:(:STR:(:INDEX: LinkedAreas))
 ]

; Most global variables accessed using LDR - larger range than ADR in general

fschain         #       4       ; Ptr to chain of known Filing Systems
 [ :INDEX: fschain <> &12C
 ! 0, "Change ShowHeap fschain to &":CC:(:STR:(:INDEX: fschain))
 ]
currentfs       #       4       ; Ptr to fscb where our CSD and CSL are
tempfs          #       4       ; Ptr to fscb what is set

FileSwitch_LastNowtVar # 0

           AlignSpace   16      ; So we don't have to ADRL these objects

MaxHandle       *       255     ; Biggest valid handle that I will return
streamtable_size *      256*4   ; Table is still indexable with 00..FF though
streamtable     #       streamtable_size
 [ :INDEX: streamtable <> &140
 ! 0, "Change ShowHeap streamtable to &":CC:(:STR:(:INDEX: streamtable))
 ]

 [ sparebuffer
SpareBufferArea #       1024 + 4*4
 ASSERT bcb_size = 4*4
SpareBuffer     #       4
 ]

; Byte size global variables

fsnumjustkilled #       1       ; Filing System no was current but just killed
                                ; Must be initialised to 0 at init
copy_n_upcalls  #       1       ; Reference count on UpCallV
                                ; Must be initialised to 0 at init and reset

 [ :LNOT: AssemblingArthur
           AlignSpace
EnvStringAddr   #       4       ; Only needed when we're not bound to kernel
EnvTimeAddr     #       4
 ]


; No need to align end of global workspace; saves alloc when rounded!

 [ :INDEX: @ > 4096
 ! 1, "FileSwitch global workspace is out of LDR range by ":CC:(:STR:((:INDEX: @)-4096)):CC:" bytes"
 ]

 [ anyfiledebug
 ! 0, "FileSwitch global area is ":CC:(:STR:(:INDEX: @)):CC:" bytes"
 ]

FileSwitchWorkspace_size * :INDEX: @


StaticName_length *     256     ; Limits size of input filename, path etc.

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Local workspace allocation: done bottom up!


; Stuff that only wants to be local during Copy

copylocalframesize *    &158    ; Change this if changing ANY frame size


                ^       -copylocalframesize, fp

 [ (:INDEX: @) :AND: 3 <> 0
 ! 1, "FileSwitch local copy frame is not word aligned"
 ]


copy_startedtime #      8       ; Top 3 bytes cleared to zero when used

dst_copy_block  #       copy_size

copy_area       #       4       ; Address of block (rma,apl,user,wimp) used in copying
copy_blocksize  #       4

copy_userbuffer     #   4
copy_userbuffersize #   4

copy_wimpfreearea   #   4
copy_wimpfreesize   #   4

copy_aplsize    #       4
copy_rmasize    #       4

copy_realload   #       4       ; Copy of info that we read so we can do
copy_realexec   #       4       ; date comparison after possible restamp


; Byte sized variables

copy_srchandle  #       1       ; Handle of file if copying by GBPB
copy_dsthandle  #       1

copy_src_dst_flag #     1       ; States as below:
copy_at_source  * 0
copy_at_dest    * 1
copy_at_unknown * &FF

copy_owns_apl_flag #    1       ; 0 -> apl unused, NE -> apl claimed

           AlignSpace   4

; .............................................................................
; Stuff that only wants to be local during FSUtils

 [ (:INDEX: @) :AND: 3 <> 0
 ! 1, "FileSwitch local utils frame is not word aligned"
 ]

utilslocalframesize *   0 - :INDEX: @


util_block      #       copy_size ; Used for src in copy. Ptrs to curr strings

util_times      #       0
util_starttime  #       8       ; Top 3 bytes cleared to zero when used
util_endtime    #       8       ; Top 3 bytes cleared to zero when used

util_ndir       #       4       ; Global to util operation
util_nfiles     #       4
util_totalsize  #       4

util_bitset     #       4       ; Localled in dir recursion
util_direntry   #       4
util_nskipped   #       4

util_load       #       4       ; Info on object (those that we will put on
util_exec       #       4       ; the dest in copy; possibly restamped)
util_length     #       4
util_attr       #       4
util_objecttype #       4
util_objectname #       64+1    ; Only allows 64 char names as yet
                                ; so ROMFS might have a chance
           AlignSpace   4       ; ROMFS does word align at odd places
util_objblksize *       @ - util_load

           AlignSpace   4

; .............................................................................
; Standard local frame workspace definitions

 [ (:INDEX: @) :AND: 3 <> 0
 ! 1, "FileSwitch local frame is not word aligned"
 ]

localframesize  *       0 - :INDEX: @


; Local variables always accessed using LDR - larger range than ADR in general

globalerror     #       4       ; Tell us if an error has happened this call
                                ; (what a silly name for a local variable !)

fsforthisop     #       4       ; Ptr to fscb for the current operation

specialptr          #   4       ; Ptr to special field
speciallength       #   4       ; Length of the special field
strippedfilenameptr #   4       ; Ptr to strippedname: set by SetFSForThisOp
                                ; (ptr into some other string)
leafnameptr     #       4       ; Ptr to leafname    : set by PoliceName
                                ; (ptr into some other string)

pathelementptr  #       4       ; Ptr to current element to use from path

commandtailptr      #   4       ; Points after filename in CommandLine in RUN

PathStringOffset  #     4       ; Ptr to current path string in PassedFilename

osfind_specialptr #     4

endptr          #       4       ; Extent to set after good GBPB op completion
memadr_filebase #       4       ; Core base of where fileptr 0 would be


; Byte size variables

; State set by PoliceName

pathnailed      #       1       ; NE -> path fixed by pathname, EQ -> not fixed
wildleafname    #       1       ; NE -> leafname is wild, EQ -> isn't
hadfsprefix     #       1       ; NE -> fsforop from prefix, EQ -> no prefix

lookforplingrun #       1       ; 1 -> look for !Run in dirs on path
pathnotended    #       1       ; NE -> path still ok, EQ -> it's all over

lowfile_opt1    #       1       ; Used for OPT 1 state in CallFSFile

osfind_objecttype #     1

           AlignSpace   4


; Linked area objects for reentrancy. Pointed to using ADR as well as LDR

; NB. Must only have one use per local frame as these are not themselved linked

TransientBlock  #       4       ; Ptr to current transient block
PassedFilename  #       4       ; Ptr to passed filename after translation
                                ; and copying into buffer
PassedFilename2 #       4       ; For copy
FullFilename    #       4       ; Ptr to full filename of current file
                                ; after copying into buffer
SpecialField      #     4       ; Ptr to a copy of the special field (or 0)
SpecialField2     #     4       ; Ptr to a copy of the other special field
                                ; eg for Rename, Copy
CommandLine       #     4       ; Ptr to a copy of command line for *RUN
PathString        #     4       ; Ptr to current path buffer
ActionVariable    #     4       ; Ptr to Load/Run action string
OptFilenameString #     4       ; Ptr to leafname returned for OPT


; Local blocks accessed by ADR

fileblock_base  #       0       ; OSFile parm dump area
fileblock_load  #       4
fileblock_exec  #       4
fileblock_length #      4
fileblock_attr  #       4
fileblock_start *       fileblock_length ; For save/create ops
fileblock_end   *       fileblock_attr

gbpbblock_base  #       0       ; OSGBPB parm dump area
gbpb_memadr     #       4
gbpb_nbytes     #       4
gbpb_fileptr    #       4


 [ anyfiledebug
 ! 0,"FileSwitch local frame is ":CC:(:STR:localframesize):CC:" bytes"
 ! 0,"FileSwitch local utils frame is ":CC:(:STR:utilslocalframesize):CC:" bytes"
 ! 0,"FileSwitch local copy frame is ":CC:(:STR:copylocalframesize):CC:" bytes"
 ]

 assert :INDEX: @ = 0           ; Local frames must all end here

 [ :INDEX: @ <> 0
 ! 0,"FileSwitch frame sizes out by":CC(:STR:(:INDEX:@)):CC:" bytes"
 ]

; *****************************************************************************
; ***                                                                       ***
; ***                  FileSwitch Module code starts here                   ***
; ***                                                                       ***
; *****************************************************************************

; Header bits for gluing into Arthur ROM or running standalone in RAM

 [ AssemblingArthur
        ALIGN

        DCD     NextModuleInImage-FSModule      ; Chain more modules after this
FSModule

Module_BaseAddr SETA .
 |
Module_BaseAddr * .
 ]

        DCD     0 ; Not an application
        DCD     FileSwitch_Init     - Module_BaseAddr
        DCD     FileSwitch_Die      - Module_BaseAddr
        DCD     FileSwitch_Service  - Module_BaseAddr
        DCD     FileSwitch_Title    - Module_BaseAddr
        DCD     FileSwitch_HelpText - Module_BaseAddr
        DCD     FileSwitch_HC_Table - Module_BaseAddr

FileSwitch_HelpText
        DCB     "FileSwitch"
        DCB     TAB
        DCB     "$FileSwitchVerNo.$FileSwitchMinor ($CurrentDate)"
 [ anyfiledebug
        DCB     " assembled at $CurrentTime"
 ]
        DCB     0


FileSwitch_HC_Table ; Name Max Min

        Command Access,    2, 1
        Command Cat,       1, 0         ; >>>a186<<< had wrong no of args
        Command CDir,      2, 1
 [ hascopy
        Command Copy,    &FF, 2         ; Interprets options on line
 ]
 [ hascount
        Command Count,   &FF, 1         ; Interprets options on line
 ]
        Command Dir,       1, 0
        Command EnumDir,   3, 2
        Command Ex,        1, 0
        Command FileInfo,  1, 1
        Command Info,      1, 1
        Command LCat,      1, 0         ; >>>a186<<< had wrong no of args
        Command LEx,       1, 0
        Command Lib,       1, 0
        Command Rename,    2, 2
        Command Run,     &FF, 1         ; Passes rest of line to command
        Command SetType,   5, 2         ; Could have 'a b c d' as file type
        Command Shut,      0, 0
        Command ShutDown,  0, 0
        Command Stamp,     1, 1
        Command Up,        1, 0
 [ haswipe
        Command Wipe,    &FF, 1         ; Interprets options on line
 ]

; Configuration commands

        Command FileSystem, 0, 0, Status_Keyword_Flag

        DCB     0                       ; That's all folks !

        GET     $fileprefix.TokHelpSrc

FileSwitch_Title
        DCB     "FileSwitch", 0

        ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Possible startup / reset sequences :
;
; 1) Hard-Break  : rom_HardInit, rom_ResetService
;
; 2) Load image  : old_HardDie, new_HardInit
;
; 3) Reinitialise: HardDie, HardInit
;
; 4) Soft-Break  : ResetService
;
; 5) RMA Tidy    : SoftDie, SoftInit
;
; 6) RMFaster    : rom_HardDie, rma_HardInit - which we must fault
;
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Init entry. r0-r6 trashable

; I can now just about survive RMTidy, I guess !

FileSwitch_Init ENTRY "r7"

        LDR     r2, [r12]               ; Is this a hard initialisation ?
        CMP     r2, #0                  ; r2 = 0 -> hard. VClear
        BEQ     %FT01

        MOV     wp, r2                  ; SoftInit
 [ debuginit
 DLINE "Soft Init"
 ]
        BL      FileSwitch_ResetInitCommon
        EXIT



01 ; Get some workspace from the RMA first of all

 [ AssemblingArthur
        addr    r14, Module_BaseAddr    ; Some twat tried to *RMFaster us ?
        CMP     r14, #ROM
        BHS     %FT01

        MOV     r0, #ModHandReason_ReInit ; Start ROM FileSwitch up again
        addr    r1, FileSwitch_Title
        SWI     XOS_Module

; NB. it is not possible to preserve current filing system over this as the
; ROM FileSwitch is dead and devectored

        ADRVC   r0, ErrorBlock_CantRMFasterFileSwitch
        SETV
        EXIT                            ; Stop this one initialising

        MakeErrorBlock CantRMFasterFileSwitch

01
 ]

 [ debuginit
 DLINE "Hard Init"
 ]
        MOV     r0, #ModHandReason_Claim
        LDR     r3, =FileSwitchWorkspace_size
        SWI     XOS_Module
        EXIT    VS

01      STR     r2, [r12]               ; Update ptr to workspace
        MOV     wp, r2

 [ :LNOT: AssemblingArthur
        SWI     XOS_GetEnv              ; Read these to gain MOS independence
        STR     r0, EnvStringAddr       ; in soft loaded version
        STR     r2, EnvTimeAddr
 ]


; Initialise lots of pointers to Nowt

        MOV     r0, #Nowt

 [ FileSwitch_LastNowtVar - FileSwitch_FirstNowtVar > 5*4
 ! 0, "Initialising ws with loop"
        ADR     r1, FileSwitch_FirstNowtVar
        ADD     r2, r1, #(FileSwitch_LastNowtVar - FileSwitch_FirstNowtVar)
05      STR     r0, [r1], #4
        TEQ     r1, r2
        BNE     %BT05
 |
 ! 0, "Initialising ws with several inline STR"
        GBLA    fsw_varcount
        WHILE   fsw_varcount < FileSwitch_LastNowtVar - FileSwitch_FirstNowtVar
        STR     r0, [wp, #:INDEX: FileSwitch_FirstNowtVar + fsw_varcount]
fsw_varcount SETA fsw_varcount + 4
        WEND
 ]


; No streams present yet

        ADRL    r0, UnallocatedStream   ; fwd ref
        ADR     r1, streamtable
        ADD     r2, r1, #streamtable_size
10      STR     r0, [r1], #4            ; Nowt scb^
        TEQ     r1, r2
        BNE     %BT10

 assert Nowt :AND: &FF = 0
        STRB    r0, fsnumjustkilled

 [ sparebuffer
        ADR     r14, SpareBufferArea    ; Nobody owns this yet
        STR     r14, SpareBuffer
        MOV     r14, #1024              ; bufmask for this buffer is permanent
        SUB     r14, r14, #1
        STR     r14, SpareBufferArea + :INDEX: bcb_actualsize
 ]

; Loop creating variables used by FileSwitch

 [ debuginit
 DLINE "Creating variables"
 ]
        ADR     r5, FileSwitch_FirstVariableToCreate
20      MOV     r0, r5                  ; Keep pointer to start of var name
        MOV     r7, r5
25      LDRB    r14, [r7], #1           ; Find start of variable value
        CMP     r14, #&FF               ; End of list ?
        BEQ     %FT40
        CMP     r14, #CR                ; End of variable name ?
        BNE     %BT25                   ; r7 -> variable value
        MOV     r5, r7                  ; Find start of next variable name
30      LDRB    r14, [r5], #1
        CMP     r14, #CR                ; End of variable value ?
        BNE     %BT30                   ; r5 -> next variable name
 [ debuginit
 DSTRING r0,"Creating variable: ",cc
 DSTRING r7,", value: "
 ]

; Check for variable prescence first. r7 -> value to set

        MOV     r6, r0                  ; Save name^
        MOV     r3, #0

34      MOV     r2, #-1                 ; r0 -> name
        SWI     XOS_ReadVarVal          ; Does it exist ? VSet misleading

        CMP     r4, #VarType_Number     ; Set if it's a number (that's silly)
        BEQ     %BT34                   ; Loop if so (maybe others)

        MOVS    r2, r2                  ; -ve -> exists
        BMI     %BT20                   ; Loop if already exists (macro,string)

35      MOV     r0, r6                  ; Restore name^
        MOV     r1, r7                  ; Restore value^
        MOV     r2, #0
        MOV     r3, #0
        MOV     r4, #VarType_String
        SWI     XOS_SetVarVal           ; r0 -> name, r1 -> value
        BVC     %BT20
        EXIT


40      BL      FileSwitch_ResetInitCommon

 [ debuginit
 DLINE "Issuing Service_FSRedeclare"
 ]
        MOVVC   r1, #Service_FSRedeclare ; Ask any Filing Systems to
        SWIVC   XOS_ServiceCall          ; (re)declare themselves to FileSwitch
                                         ; after we're on the vectors
 [ debuginit
 DLINE "Calling Init_InvalidateBGetCache"
 ]
        BL      Init_InvalidateBGetCache
        EXIT


FileSwitch_FirstVariableToCreate * .

; ************************** Path variables ***********************************

RunPathVariableNameCR   DCB     "Run$Path", CR
RunPathVariableDefault  DCB     ",%.", CR       ; null then library

FilePathVariableNameCR  DCB     "File$Path", CR
FilePathVariableDefault DCB     "", CR          ; just null

; ************************** Utility Options **********************************

 [ hascopy
CopyOptsVariableNameCR  DCB     "Copy$Options", CR
CopyOptsVariableDefault DCB     "A C ~D ~F ~L ~N ~P ~Q ~R ~S ~T V", CR
 ]

 [ haswipe
WipeOptsVariableNameCR  DCB     "Wipe$Options", CR
WipeOptsVariableDefault DCB     "C ~F ~R V", CR
 ]

 [ hascount
CountOptsVariableNameCR  DCB    "Count$Options", CR
CountOptsVariableDefault DCB    "~C R ~V", CR
 ]

; ************************** Aliases for Run actions **************************

; (need space on end of ones without %*1 to get syntax error out of command
;  line decoder; would just append to file arg otherwise)

; No run action for BBC BBC ROM
;                   FE0 DeskUtil

; FE1..FEA not yet allocated

        DCB     "Alias$@RunType_FEB", CR  ; Obey
        DCB     "Obey %0 ", CR

; No run action for FEC Template

        DCB     "Alias$@RunType_FED", CR  ; Palette
        DCB     "WimpPalette %0 ", CR

; No run action for FEE Note pad
;                   FEF Diary

; FF0..FF4 not yet allocated

; No run action for FF5 PoScript

; No run action for FF6 Font

        DCB     "Alias$@RunType_FF7", CR  ; BBC font
        DCB     "Print %0 ", CR

; No run action for FF8 Absolute (can't be aliased)

        DCB     "Alias$@RunType_FF9", CR  ; Sprite
        DCB     "ScreenLoad %0 ", CR

        DCB     "Alias$@RunType_FFA", CR  ; Module
        DCB     "RMRun %*0", CR

        DCB     "Alias$@RunType_FFB", CR  ; Basic
        DCB     "Basic -quit ""%0"" %*1", CR

; No run action for FFC Transient (can't be aliased)
;                   FFD Data

        DCB     "Alias$@RunType_FFE", CR  ; Command
        DCB     "Exec %0 ", CR

        DCB     "Alias$@RunType_FFF", CR  ; Text
        DCB     "Type %0 ", CR

; *********************** Aliases for Load actions ****************************

; No load action for BBC BBC ROM
;                    FE0 DeskUtil deprecated

; FE1..FEA not yet allocated

; No load action for FEB Obey
;                    FEC Template
;                    FED Palette
;                    FEE Note pad deprecated
;                    FEF Diary    deprecated

; FF0..FF4 not yet allocated

; No load action for FF5 PoScript

; No load action for FF6 Font

        DCB     "Alias$@LoadType_FF7", CR ; BBC font
        DCB     "Print %0 ", CR

; No load action for FF8 Absolute (can't be aliased)

        DCB     "Alias$@LoadType_FF9", CR ; Sprite
        DCB     "SLoad %0 ", CR

        DCB     "Alias$@LoadType_FFA", CR ; Module
        DCB     "RMLoad %*0", CR

        DCB     "Alias$@LoadType_FFB", CR ; Basic
        DCB     "Basic -load ""%0"" %*1", CR

; No load action for FFC Transient (can't be aliased)
;                    FFD Data
;                    FFE Exec
;                    FFF Text

; ****************************** File type ************************************

        DCB     "File$Type_BBC", CR
        DCB     "BBC ROM", CR

; FE0 DeskUtil deprecated

; FE1..FEA not yet allocated

        DCB     "File$Type_FEB", CR
        DCB     "Obey", CR

        DCB     "File$Type_FEC", CR
        DCB     "Template", CR

        DCB     "File$Type_FED", CR
        DCB     "Palette", CR

; FEE Note pad  deprecated
; FEF Diary     deprecated

; FF0..FF4 not yet allocated

        DCB     "File$Type_FF5", CR
        DCB     "PoScript", CR

        DCB     "File$Type_FF6", CR
        DCB     "Font", CR

        DCB     "File$Type_FF7", CR
        DCB     "BBC font", CR

        DCB     "File$Type_FF8", CR
        DCB     "Absolute", CR

        DCB     "File$Type_FF9", CR
        DCB     "Sprite", CR

        DCB     "File$Type_FFA", CR
        DCB     "Module", CR

        DCB     "File$Type_FFB", CR
        DCB     "BASIC", CR

        DCB     "File$Type_FFC", CR
        DCB     "Utility", CR

        DCB     "File$Type_FFD", CR
        DCB     "Data", CR

        DCB     "File$Type_FFE", CR
        DCB     "Command", CR

        DCB     "File$Type_FFF", CR
        DCB     "Text", CR

        DCB     &FF             ; End of variable (name,value) list
        ALIGN

        LTORG

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; FileSwitch_ResetInitCommon
; ==========================

; In    no parms

; Out   VC -> ok
;       VS -> fail
;       r0-r2 trashed

FileSwitch_ResetInitCommon ENTRY

 [ debuginit
 DLINE "ResetInitCommon"
 ]
        MOV     r14, #0                 ; Not on UpCallV
        STRB    r14, copy_n_upcalls

        LDR     r14, currentfs          ; Reset temp FS to currently selected
        STR     r14, tempfs

        BL      SitOnVectors
        EXIT

        LTORG

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Die entry. r0-r6 trashable

; I think I can survive RMTidy now !

FileSwitch_Die ROUT

        LDR     wp, [r12]

        LDR     r0, LinkedAreas         ; Can't be killed whilst threaded
        TEQ     r0, #Nowt
        ADRNE   r0, ErrorBlock_FileSwitchCantBeKilledWhilstThreaded
        ORRNES  pc, lr, #V_bit

        ENTRY   "fp"
        MOV     fp, #0                  ; No frame, so don't set globalerror
 assert fp <> r10

 [ False ; By virtue of the above test, this can never be of use >>>a186<<<
        BL      SFreeAllLinkedAreasEverywhere ; ALL domains dying
 ]
        BL      DelinkTheLot            ; Need to devector during any death

        CMP     r10, #0                 ; Non-fatal death ?
        EXIT    EQ                      ; VClear

 [ debuginit
 dline "FileSwitch fatal death"
 ]
        BL      CloseAllFilesEverywhere ; Just make sure they're all dead

        LDR     r2, fschain             ; Point to first fscb
50      CMP     r2, #Nowt               ; End of fs chain ?
        EXIT    EQ                      ; VClear
        LDR     r3, [r2, #fscb_link]    ; Remove link before deallocation
        BL      SFreeArea
        MOV     r2, r3                  ; Ignore errors, keep on going
        B       %BT50

        LTORG

        MakeErrorBlock FileSwitchCantBeKilledWhilstThreaded

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Service entry. No trashable registers. Services can't give errors back

FileSwitch_Service ROUT

        CMP     r1, #Service_Memory
        BEQ     FileSwitch_Service_Memory

        CMP     r1, #Service_Reset      ; CSet,EQ
        TEQNE   r1, #Service_StartUpFS*4,2 ; CClear,EQ. Dead sexy, huh ?
        MOVNE   pc, lr

        BCS     FileSwitch_Service_Reset ; Occurs infrequently!


; Select Filing System by number

FileSwitch_Service_StartUpFS ENTRY "r0, r2"

        MOV     r0, #FSControl_SelectFS
        AND     r1, r2, #&FF
        SWI     XOS_FSControl           ; Default -> no error, but may be vect.
        MOV     r1, #Service_Serviced   ; Claim the service
        EXIT

; .............................................................................

FileSwitch_Service_Reset ENTRY "r0-r2, fscb, fp"

        LDR     wp, [r12]
        MOV     fp, #0                  ; No frame, so don't set globalerror 

        BL      SFreeAllLinkedAreasEverywhere ; Ensure clean start

        BL      FileSwitch_ResetInitCommon

        LDR     fscb, currentfs         ; Give fs banner
        BL      PrintFilingSystemText
        EXIT

; .............................................................................
; We must forbid memory being taken away from us if we are using apl

; In    r0 = amount of memory to ADD to apl (may be -ve: normal)
;       r2 = CAO pointer

FileSwitch_Service_Memory ROUT

 [ debugservice
 DREG r2,"Memory moving service: CAO "
 ]
        TEQ     r0, #0                  ; Can always ADD to apl size
        MOVPLS  pc, lr                  ; when we own it, just forbid takeaway

; It would be nice to allow memory to be taken away up to the point where
; we are currently using for the copy buffer, but as we don't have any
; means of domain identification, I refuse to consider this.

        addr    r12, Module_BaseAddr    ; We are CAO when doing Copy Q
        CMP     r2, r12                 ; on a per domain basis
        ADRHSL  r12, FileSwitch_ModuleEnd
        CMPHS   r12, r2
        MOVHS   r1, #Service_Serviced   ; Claim service - CAO is us
 [ debugservice
 TEQ r1, #Service_Serviced
 BNE %FT00
 DLINE "Forbidding memory move: FileSwitch owns this domain's apl"
00
 ]
        MOVS    pc, lr                  ; VClear

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Vector claim/release routines

SitOnVectors ENTRY "r0-r4"

        ADR     r4, FileSwitch_VectorTable
        ADR     r3, FileSwitch_VectorNumberTable

10      LDRB    r0, [r3], #1            ; Load vector number
        CMP     r0, #&FF                ; End entry ?
        EXIT    EQ

 [ AssemblingArthur
        TST     r0, #&80                ; If not BGet or BPut, always vector
        BEQ     %FT20

        LDR     r14, =fileswitch_ws1    ; Defined in ArthurSWIs
        CMP     r14, wp
        BEQ     %BT10                   ; [got expected wp, so don't vector]

        BIC     r0, r0, #&80
20
 ]
        LDR     r14, [r4], #4           ; Form address of routine
        ADD     r1, r4, r14             ; NB. r2 advanced !
        MOV     r2, wp
 [ debugvector
 DREG r0, "Claiming vector ",cc,Byte
 DREG r1, ", address ",cc
 DREG r2, ", ws^ "
 ]
        SWI     XOS_Claim               ; Ensure on only once
        BVC     %BT10                   ; Loop if ok

        STR     r0, [sp]
        BL      DelinkTheLot            ; Preserves V
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Out   V preserved

DelinkTheLot ENTRY "r0-r4"

        ADR     r4, FileSwitch_VectorTable
        ADR     r3, FileSwitch_VectorNumberTable

10      LDRB    r0, [r3], #1            ; Load vector number
        CMP     r0, #&FF                ; End entry ?
        EXITS   EQ

 [ AssemblingArthur
        TST     r0, #&80                ; If not BGet or BPut, always unlink
        BEQ     %FT20

        LDR     r14, =fileswitch_ws1    ; Defined in ArthurSWIs
        CMP     r14, wp
        BEQ     %BT10                   ; [got expected wp, so not vectored]

        BIC     r0, r0, #&80
20
 ]
        LDR     r14, [r4], #4
        ADD     r1, r4, r14             ; Form address of routine
        MOV     r2, wp
 [ debugvector
 DREG r0, "Freeing vector ",cc,Byte
 DREG r1, ", address ",cc
 DREG r2, ", ws^ "
 ]
        SWI     XOS_Release
        B       %BT10                   ; Loop

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Vectors claimed (released) on FileSwitch initialisation and reset (death)

FileSwitch_VectorTable

        DCD     FSControlEntry  -.-4    ; Pretty naff addressing, huh ?
        DCD     FileEntry       -.-4    ; Goes with the pretty naff code
        DCD     FindEntry       -.-4    ; ie. LDR rn, [r2], #4
        DCD     MultipleEntry   -.-4
        DCD     ArgsEntry       -.-4
        DCD     BGetEntry       -.-4
        DCD     BPutEntry       -.-4

FileSwitch_VectorNumberTable

        DCB     FSCV
        DCB     FileV
        DCB     FindV
        DCB     GBPBV
        DCB     ArgsV
 [ AssemblingArthur
        DCB     BGetV :OR: &80          ; Only claim if wp~=expected if in ROM
        DCB     BPutV :OR: &80
 |
        DCB     BGetV                   ; Always claim if not in ROM
        DCB     BPutV
 ]

        DCB     &FF                     ; End of table marker
        ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                           E X I T   P O I N T S
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    lr(Punter) stacked. This has his NZVC state, so be careful.
;       r14 -> errorblock, 0 if no error

FileSwitchExit ROUT

 [ paranoid
 Push r14
 LDR r14,LinkedAreas ; Can only debug globals here !
 TEQ r14,#Nowt
 BEQ %FT00
 DLINE "LinkedAreas not empty",,inv
00
 Pull r14
 ]
        TEQ     r14, #0
 [ anyfiledebug
 BEQ %FT00
 DREG r14, "globalerror on exit "
00
 ]
        Pull    lr, EQ
        BICEQS  pc, lr, #V_bit          ; Return to punter, NZC flags intact

; .............................................................................
; In    r14 -> error block

LethalFileSwitchExit

        MOV     r0, r14                 ; r0 -> error block
        Pull    lr
        ORRS    pc, lr, #V_bit          ; Return to punter, NZC flags intact

; .............................................................................
; In    C flag significant and should be returned to punter
;       r14 -> error block, 0 if no error

FileSwitchExitSettingC ROUT

        TEQ     r14, #0                 ; Careful to preserve C !
        BNE     LethalFileSwitchExit

        Pull    lr
        BIC     lr, lr, #V_bit          ; ??? Does this have VClear anyway ?
        BICCCS  pc, lr, #C_bit          ; Return to punter, NZ flags intact
        ORRCSS  pc, lr, #C_bit


        LNK     $fileprefix.FSCommands
