        SUBT    > <wini>arm.FileSwitch.StreamBits - Stream and buffer handling

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; +                                                                           +
; +              All the nasty bits to do with stream handling                +
; +                                                                           +
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Offsets in a stream control block

                ^       0, scb
scb_status      #       4       ; Stream status bits         }
scb_fileptr     #       4       ; Current PTR# within file   } Keep together
scb_extent      #       4       ; Current EXT# of file       }   in order
scb_bufmask     #       4       ; Size of buffers to use     } at bottom of
scb_bcb         #       4       ; Ptr to associated bcb list } scb (LDMIA)

scb_shift       #       4       ; log2(buffersize)
scb_exthandle   #       4       ; External handle, as seen by the punter

scb_fshandle    #       4       ; Real Filing System handle  } Keep together
scb_fscb        #       4       ; Ptr to associated fscb     }   in order

scb_devid       #       4       ; Stream identifier, normally zero
scb_allocsize   #       4       ; Size currently allocated on disc

scb_size        *       :INDEX: @


; Bits in scb_status - low set blocked (necessary for open r0 bits)

scb_datalost    * 1 :SHL: 13 ; 1 if stream is dead meat - it got unlucky!
scb_critical    * 1 :SHL: 12 ; 1 if stream is in critical region
; Can do       TST   rn, #bits 4..11  in one go
; Must then do TSTEQ rn, #bits 12..13 if we're interested in them too
scb_unallocated * 1 :SHL: 11 ; 1 if stream is not allocated

; **** MUST keep this set together in this order for OS 1.20 compatibility ****

scb_unbuffered  * 1 :SHL: 10 ; 1 if stream is unbuffered, 0 if it is buffered
scb_EOF_pending * 1 :SHL: 9  ; 1 if we have already read at EOF on this stream
scb_modified    * 1 :SHL: 8  ; 1 if we have we written to this file, 0 if not

scb_write       * 1 :SHL: 7  ; 1 if we have write access, 0 if not
scb_read        * 1 :SHL: 6  ; 1 if we have read access, 0 if not
scb_directory   * 1 :SHL: 5  ; 1 if it's a directory, 0 if not
scb_unbuffgbpb  * 1 :SHL: 4  ; 1 if stream directly supports gbpb, 0 if not
scb_interactive * 1 :SHL: 3  ; 1 if stream is interactive, 0 if not

; *****************************************************************************

; bits 0..2 unallocated

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; AllocateStream
; ==============
;
; Look for an empty handle, attempt to get an scb, but don't put scb in
; stream table here as it's not altogether kosher until all the fields are
; filled. However, we can calculate and insert the backlink to the stream table
; Must also have Nowt bcb^ for if we need to deallocate prior to insertion.

; Out   VC: r3 = external handle and scb^ allocated
;       VS: failed to allocate

AllocateStream ENTRY "r1, r2, r4"

 [ debugstream
 DLINE "Entering AllocateStream"
 ]
        MOV     r1, #MaxHandle          ; Allocate handles from MaxHandle..1
        ADR     r2, UnallocatedStream
        ADR     r4, streamtable
05      LDR     r14, [r4, r1, LSL #2]   ; Empty slot found ?
        TEQ     r14, r2
        BEQ     %FT20
        SUBS    r1, r1, #1
        BNE     %BT05                   ; If reached handle 0, naff up

10      addr    r0, ErrorBlock_TooManyOpenFiles
        BL      CopyError
        EXIT


; Try to get space for scb, and stick the backlink in

20      MOV     r3, #scb_size
        BL      SGetArea
        EXIT    VS
        BEQ     %BA10                   ; Now we can distiguish HeapFails
        MOV     scb, r2                 ; (NE = ok)

        STR     r1, scb_exthandle       ; Remember what handle we gave it

        MOV     r14, #Nowt              ; No associated buffers yet !
        STR     r14, scb_bcb

 [ debugstream
 DREG r1, "Allocated handle ",cc
 DREG scb, ", scb "
 ]
        MOV     r3, r1                  ; Where caller prefers it
        EXIT


UnallocatedStream                       ; >>>a186<<< ensure ROM scb ok
 ASSERT .-UnallocatedStream = :INDEX: scb_status
        DCD     scb_unallocated         ; His is not a sausage
 ASSERT .-UnallocatedStream = :INDEX: scb_fileptr
        DCD     0                       ; Also need fileptr < extent so we
 ASSERT .-UnallocatedStream = :INDEX: scb_extent
        DCD     &100                    ; don't write back in ReadStreamInfo!

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; DeallocateStream
; ================
;
; Attempt to free buffer and scb and zap stream table.

; Check FileSwitch handle against Exec/Spool handles and zero them if needs be.

; Also redirection handles? But Sam kills redirection on errors anyway.
; Do this here rather than in a higher level routine as this gets called for
; all close operations.

; In    scb^ valid

; Out   VC: ok
;       VS: fail

DeallocateStream ENTRY "r0-r4, bcb"

 [ debugstream
 DREG scb,"Deallocating scb ",cc
 LDR r14,scb_exthandle
 DREG r14,", ext handle "
 ]
        LDRB    r4, scb_exthandle
        ADR     r0, streamtable         ; Remember external handle for below
        ADR     r14, UnallocatedStream  ; Free entry in stream table
        STR     r14, [r0, r4, LSL #2]

        MOV     r0, #198                ; Read EXEC and SPOOL file handles
        MOV     r1, #0
        MOV     r2, #255
        SWI     XOS_Byte
        MOV     r3, r1                  ; Hide this away in case we close SPOOL

        CMP     r2, r4                  ; Same as SPOOL handle ?
        MOVEQ   r0, #199                ; Zero SPOOL handle if so
        MOVEQ   r1, #0
        MOVEQ   r2, #0
        SWIEQ   XOS_Byte

        CMP     r3, r4                  ; Same as EXEC handle ?
        MOVEQ   r0, #198                ; Zero EXEC handle if so
        MOVEQ   r1, #0
        MOVEQ   r2, #0
        SWIEQ   XOS_Byte

        LDR     bcb, scb_bcb            ; Free buffer, if there is one
        CMP     bcb, #Nowt              ; VClear
        BLNE    DeallocateBuffer

        MOV     r2, scb                 ; Free scb space
        BL      SFreeArea               ; Accumulates V
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; FindStream
; ==========
;
; Find the scb associated with a FileSwitch handle

; In    r1[1..MaxHandle] = FileSwitch handle

; Out   VClear always
;       NE: scb^
;       EQ: no stream on this handle, scb -> UnallocatedStream

FindStream ENTRY

        ADR     r14, streamtable
        LDR     scb, [r14, r1, LSL #2]
        ADR     r14, UnallocatedStream
        CMP     scb, r14                ; Valid scb^ ? VClear
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; FindValidStream
; ===============
;
; Validate a FileSwitch handle and find the scb associated with it

; In    r1b = FileSwitch handle

; Out   VC: scb^, flags preserved
;       VS: no stream on this handle

FindValidStream ENTRY "r1"

        AND     r1, r1, #&FF            ; Mask external handles with &FF always
        ADR     r14, streamtable
        LDR     scb, [r14, r1, LSL #2]
        ADR     r14, UnallocatedStream
        CMP     scb, r14                ; Valid scb^ ? VClear
        EXITS   NE                      ; Preserve caller flags if ok
                                        ; (makes life easier up there)
        BL      SetErrorChannel
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Offsets in a buffer control block

                ^       0, bcb
bcb_actualsize  #       4       ; bufmask that buffer was allocated with
bcb_status      #       1       ; Buffer validity / modified flags
                #       3
bcb_bufferbase  #       4       ; PTR# of buffer in file
bcb_dataoffset  #       4       ; Hmm. Do LDR r14, bcb_dataoffset
                                ;         SUB r14, fileptr, r14
                                ;         xxRB r0, [bcb, r14]
bcb_bufferdata  #       0       ; Actual data in the buffer. Word aligned

bcb_size        *       :INDEX: @


; Bits in bcb_status

bcb_valid       *       1 :SHL: 1 ; Are the buffer contents valid ?
bcb_modified    *       1 :SHL: 0 ; Have we written to this buffer ?

bcb_status_bits *       bcb_valid :OR: bcb_modified

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; AllocateBuffer
; ==============
;
; Get a new buffer for this stream

; In    bufmask+1 = size of buffer wanted, scb^ valid

; Out   VC: bcb^ -> new buffer, put on bcb list
;       VS: failed to allocate buffer

AllocateBuffer ENTRY "r0, r2-r3"

 [ debugstream
 DREG bufmask,"AllocateBuffer: bufmask "
 ]
        BL      ClaimBuffer             ; Go and get a buffer
        EXIT    VS

        MOV     r14, #0                 ; Buffer contents not valid
        STRB    r14, bcb_status         ; or modified (by definition)

        MOV     r14, #-1                ; Buffer is not in the file
        STR     r14, bcb_bufferbase

        STR     bcb, scb_bcb            ; bcb now valid enough to be added in
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    scb^, bufmask valid

; Out   bcb allocated

ClaimBuffer ENTRY "r0-r3, bufmask"

 [ debugstream
 DREG bufmask,"ClaimBuffer: bufmask "
 ]
        ADD     r3, bufmask, #bcb_size+1 ; Try system heap first
        BL      SGetArea
        EXIT    VS                      ; [bad fail]
        MOVNE   bcb, r2
        STRNE   bufmask, bcb_actualsize ; Remember size of block
        EXIT    NE

 [ sparebuffer
        LDR     bcb, SpareBuffer        ; Try our spare buffer
        TEQ     bcb, #Nowt
        MOVNE   r14, #Nowt
        STRNE   r14, SpareBuffer        ; Claim it
 [ debugstream
 BEQ %FT00
 DLINE "Using SpareBuffer"
00
 ]
        EXIT    NE
 ]


10 ; Loop up from current bufmask to system limit until we find a victim

; Try flushing unmodified buffers first

 [ debugstream
 DREG bufmask, "Try flushing an unmodified buffer of size "
 ]
        MOV     r2, #bcb_modified
        BL      FlushSomeone
        EXIT    NE                      ; [flushed, bcb^ valid]

; Try flushing any buffers of the right size

 [ debugstream
 DREG bufmask, "Try flushing any buffer of size "
 ]
        MOV     r2, #0
        BL      FlushSomeone
        EXIT    NE                      ; [flushed, bcb^ valid]

        MOV     bufmask, bufmask, LSL #1 ; Try next size up
        ORR     bufmask, bufmask, #1
        CMP     bufmask, #1024
        BLO     %BT10                   ; [not reached limit yet]


90      ADR     r14, errorbuffer        ; Regenerate 'Heap Full' error
        STR     r14, globalerror        ; fp is valid, of course ...
        SETV
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Loop over streamtable and see if there are any streams that can be flushed

; In    scb^ valid, bufmask = size wanted, r2 = bcb_status faulty bits

; Out   VClear always - may cause errors on OTHER streams, but we're ok!
;       EQ: no stream flushed
;       NE: stream flushed, bcb -> buffer freed for our use

FlushSomeone ENTRY "r0-r3, bufmask, scb"

        LDRB    r1, scb_exthandle       ; Start at this handle
        MOV     r3, r1

10      SUBS    r3, r3, #1              ; Wrap from 1 back to MaxHandle
        MOVEQ   r3, #MaxHandle

        CMP     r3, r1                  ; If we've reached same handle, error
        EXIT    EQ                      ; VC, EQ -> no luck this time

        ADR     r14, streamtable        ; Like FindStream but with r3
        LDR     scb, [r14, r3, LSL #2]

        LDR     r14, scb_status
        TST     r14, #scb_unallocated :OR: scb_unbuffered :OR: scb_directory
        TSTEQ   r14, #scb_datalost :OR: scb_critical
        BNE     %BT10                   ; This scb, he got duff status bits

        LDR     bcb, scb_bcb
        TEQ     bcb, #Nowt
        BEQ     %BT10                   ; This scb, he got no buffer anyway

        LDR     r14, bcb_actualsize
        CMP     r14, bufmask
        BNE     %BT10                   ; This bcb, he the wrong size

        LDRB    r14, bcb_status
        TST     r14, r2
        BNE     %BT10                   ; This bcb, he got duff status bits

; Got a victim, so flush to disc, unlink from stream, then eat his brain

; <<< Question: if victim bigger, should we free to heap and then try claim
;               again (friendlier, may get us more small buffers) or keep all
;               of him (wasteful, but we can ensure that streams don't lose
;               the ability to claim large buffers again) ... unresolved
; SKS now believes the latter to be best

 [ debugstream
 LDRB r14, scb_exthandle
 DREG r14, "Victim is external handle number "
 ]
        LDR     bufmask, scb_bufmask    ; This wants to know the stream bufmask
        BL      FlushBuffer_2

; This may fail eg. Not listening, but that's tough.
; Note that globalerror also needs clearing otherwise OUR op will give an error
; rather than the other stream going 'Data lost'

        MOV     r14, #Nowt              ; Unlink buffer from stream
        STR     r14, scb_bcb

        MOV     r14, #0
        STR     r14, globalerror

        CMP     pc, #0                  ; VC, NE -> flushed one ok
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; DeallocateBuffer
; ================
;
; Got bored with this one; let's get rid of it (don't care about the data)

; In    scb^ valid, bcb^ to deallocate

; Out   V accumulated

DeallocateBuffer ENTRY "r2"

 [ debugstream
 DREG bcb,"Deallocating bcb "
 ]
        MOV     r2, bcb                 ; Prepare for release !

        MOV     r14, #Nowt              ; Unlink buffer from stream
        STR     r14, scb_bcb

 [ sparebuffer
        ADR     r14, SpareBufferArea    ; Freeing our spare ?
        CMP     r14, r2                 ; VClear
        STREQ   r14, SpareBuffer
        BLNE    SFreeArea
 |
        BL      SFreeArea
 ]
        EXITS   VC                      ; Accumulate V
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; FindFileBuffer
; ==============
;
; Look down bcb list of stream to see if there is a buffer at given file posn

; In    fileptr = position, scb and bufmask valid

; Out   EQ: not found, bcb^ = Nowt
;       NE: found, bcb^ -> buffer control block
; VClear always

FindFileBuffer ENTRY "r0"

        BIC     r0, fileptr, bufmask    ; Where buffer base would be
 [ debugstream
 DREG fileptr,"Looking for buffer at PTR ",cc
 DREG r0,", with base ",cc
 ]

        LDR     bcb, scb_bcb
        CMP     bcb, #Nowt
 [ debugstream
 BNE %FT11
 DLINE " - buffer not found"
11
 ]
        EXIT    EQ                      ; EQ -> not found, VClear

        LDR     r14, bcb_bufferbase
 [ debugstream
 DREG r14,"; Checking buffer at base ",cc
 ]
        CMP     r14, r0                 ; VClear. Swap EQ <-> NE
        MOV     r14, #Z_bit
        TEQP    r14, pc
 [ debugstream
 BNE %FT11
 DLINE " - buffer not found"
 EXIT
11
 DLINE " - buffer found"
 ]
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; GetFileBuffer
; =============
;
; Get a buffer at given place and fill with data from file if not >= extent

; In    fileptr = where to get it, scb, extent, bufmask valid

GetFileBuffer ENTRY

 [ debugstream
 DREG fileptr, "GetFileBuffer: fileptr = "
 ]
        BL      InvalidateBGetCache     ; Replacing buffer contents, so can't
                                        ; keep cache

        LDR     bcb, scb_bcb            ; Already got a buffer?
        CMP     bcb, #Nowt
        BNE     %FT10                   ; [yes]

        BL      AllocateBuffer          ; Get a new buffer please
        B       %FA20

10      BL      FlushBuffer_2           ; Dump current contents
                                        ; but keep the bcb attached
                                        ; Errors important here as they're on
                                        ; the same stream
20      EXIT    VS

        MOV     r14, #bcb_valid         ; Buffer is about to become valid
        STRB    r14, bcb_status         ; (and unmodified, of course)

        BIC     r14, fileptr, bufmask   ; Base file address of block where
        STR     r14, bcb_bufferbase     ; PTR is

        CMP     r14, extent             ; If bufferbase >= old EOF
                                        ; we don't read underlay
                                        ; (current contents will do)
 [ debugstream
 BCC %FT00
 DREG r14, "No underlay read as bufferbase ",cc
 DREG extent, " >= extent "
00
 ]
        SUB     r14, r14, #(:INDEX: bcb_bufferdata) ; Always set up dataoffset
        STR     r14, bcb_dataoffset
        EXIT    HS

        BL      CallFSGetBuffer
 [ debugosgbpbirq
 EXIT VS
 Push "r0, lr"
 MOV r0, psr
 TST r0, #I_bit
 MOVEQ r0, #"3"
 MOVNE r0, #"c"
 SWI XOS_WriteC
 Pull "r0, lr"
 ]
        BLVS    DeallocateBuffer        ; Discard invalid buffer. Accumulates V
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; EnsureBufferValidForWrite
; =========================
;
; If ensuring for write at EOF then must ask for more space on media or else
; I'd accept data that might provoke a 'Disc full' error when flushing. NB. If
; that gives an error then I'd better throw away the new buffer

; No need to write zeroes into the file though

; In    scb^ and streaminfo valid

; Out   bcb^ valid, pertaining to fileptr

EnsureBufferValidForWrite ENTRY "r1-r2"

        BL      FindFileBuffer          ; Got the right buffer here ?
        EXIT    NE                      ; [yes]

        BL      GetFileBuffer           ; Better run and get it
        EXIT    VS

        BIC     r14, fileptr, bufmask   ; Going to be writing at EOF ?
        CMP     r14, extent
        EXIT    NE

        ADD     r2, extent, #1
        ADD     r2, r2, bufmask         ; Round 0000 up to 0100
        BICS    r2, r2, bufmask
        BEQ     %FA99                   ; [wrapped 32 bits]
        BL      EnsureFileSize

        BLVS    DeallocateBuffer        ; Discard invalid buffer
        EXIT                            ; Accumulates V


99      addr    r0, ErrorBlock_FSFileTooBig
        BL      CopyError
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; EnsureBufferValid
; =================
;
; Get a valid buffer at the current file address, which mustn't be outside
; the file !

; If trying to get a buffer at extent then don't look to media for an underlay,
; but tell the Filing System we need to be able to have a file of given length

; In    scb^ and streaminfo valid

; Out   VC: bcb^ and buffer data valid, pertaining to fileptr
;       VS: failed to get buffer

EnsureBufferValid ENTRY

 [ debugstream
 DREG fileptr,"Ensure valid buffer at fileptr "
 ]
        BL      FindFileBuffer                  ; Got the right buffer here ?
        EXIT    NE

        BL      GetFileBuffer                   ; Better run and get it if not
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; DiscardBuffersBetween
; =====================
;
; Ensure that no valid buffers are present in the range r0..r1-1
; (not necessarily buffer multiples)
; Buffers discarded must be FULLY spanned by r0..r1-1

; This is a discard (not flush), called on file contraction and gbpb write

; In    r0 = lower limit, inclusive
;       r1 = upper limit, exclusive
;       scb^ and bufmask valid

DiscardBuffersBetween ENTRY "r0-r1, bcb"

 [ debugstream
 DREG r0,"Discarding buffers between ",cc
 DREG r1," and "
 ]

        LDR     bcb, scb_bcb
        CMP     bcb, #Nowt              ; No buffer ?
        EXIT    EQ                      ; VClear

        LDR     r14, bcb_bufferbase
 [ debugstream
 DREG r14,"Checking buffer at base "
 ]
        CMP     r14, r0                 ; Discard if (bs => r0) and (r1 => be)
        ADDHS   r14, r14, bufmask
        ADDHS   r14, r14, #1
        CMPHS   r1, r14
        EXITS   LO                      ; VClear

 [ debugstream
 DREG bcb,"Discarding invalidated bcb "
 ]
        CLRV                            ; Unsure about V from above CMP
        BL      DeallocateBuffer
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ExtendFileWithZeroes
; ====================
;
; Take one buffered stream, simmer for 20 mins at Gas Mark 3 ...

; In    scb^ and streaminfo valid
;       r2 = length of file to extend and fill with zeroes to

; Out   VC: new extent NOT set (for GBPB porpoises !)

ExtendFileWithZeroes ENTRY "r0-r2, fileptr, bcb, fscb" ; Nasty temporary

 [ debugstream
 DREG r2, "Extending to ",cc
 DREG extent, " from "
 ]
        CMP     r2, extent              ; File growing ?
        EXITS   LS                      ; No - assumes VClear in entry lr

        BIC     fscb, extent, bufmask   ; base of eof buffer. Useful value

; If eof unaligned then we need that buffer for writing zeroes

        ANDS    r0, extent, bufmask     ; Offset of extent in buffer
        BEQ     %FT20                   ; Partial update needed ? No, thank God

        MOV     fileptr, fscb           ; Get buffer at eof for zero extension
        BL      EnsureBufferValid
        EXIT    VS

        BL      ZeroBufferFromPosition  ; Clear from ext to eob. r0 from above

; If new eof within (or aligned at end of) old eof buffer then there is no need
; to involve Filing System at all.
; The case of new eof = base of old eof has been trapped already (Phew !)

        SUB     r14, r2, #1             ; Maps xx001..xx100 -> xx000..xx0FF
        BIC     r14, r14, bufmask       ; Map to buffer base
        CMP     r14, fscb               ; In the same one as before ?
        EXIT    EQ                      ; All done then ! VClear


20 ; Best tell the Filing System now that the file is growing ...

        ADD     r2, r2, bufmask         ; Reserve space on media
        BICS    r2, r2, bufmask
        BEQ     %FA99                   ; [wrapped 32 bits; 0 case handled ^]
        BL      EnsureFileSize
        EXIT    VS

; Deal with the last bit left over at eof by pretending it's one block bigger !

; zerostartinfile := (old.extent+bufmask) BIC bufmask
; zeroendinfile   := (new.extent+bufmask) BIC bufmask

        LDR     r14, [sp, #4*2]         ; End point (r2 in)
        ADD     r14, r14, bufmask
        BIC     r14, r14, bufmask

        ADD     r2, extent, bufmask     ; Start point
        BIC     r2, r2, bufmask

        SUBS    r1, r14, r2             ; How much to do
        BL      ZeroFileFromPosition

; In the interests of efficiency, we should really create a buffer full of
; zeroes at new eof if new eof buffer is unaligned, as this saves getting an
; underlay later when one does an op at this point.
; But only when multiple buffers arrive, otherwise gain is lost !

        EXIT


99      addr    r0, ErrorBlock_FSFileTooBig
        BL      CopyError
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ZeroBufferFromPosition
; ======================
;
; Clear out a buffer from a given position to the end (inclusive)

; In    r0 = offset in buffer;
;       bufmask and bcb valid

ZeroBufferFromPosition ENTRY "r0-r1"

        LDRB    r14, bcb_status         ; Buffer modified
        ORR     r14, r14, #bcb_modified
        STRB    r14, bcb_status

 [ debugstream
 DREG r0,"Writing zeroes to buffer at offset "
 ]
        MOV     r14, #0
        ADR     r1, bcb_bufferdata      ; r1 -> sob
        TST     r0, #3                  ; can we do it fast ?
        BEQ     %FT50

20      CMP     r0, bufmask             ; Already at eob ?
        STRLSB  r14, [r1, r0]           ; Zero last byte of buffer too !
        ADDLO   r0, r0, #1
        BLO     %BT20
        EXIT                            ; VClear

50      CMP     r0, bufmask             ; Already at eob ?
        STRLO   r14, [r1, r0]           ; Includes last word of buffer
        ADDLO   r0, r0, #4
        BLO     %BT50
        EXIT                            ; VClear

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; ZeroFileFromPosition
; ====================
;
; Write zeroes to a file from a given point

; In    r2 = offset in file
;       r1 = number of bytes to zero (can be zero)

ZeroFileFromPosition ENTRY "r0-r3"

 [ debugstream
 DREG r1,"Writing ",cc
 DREG r2," zeroes to file at "
 ]
        MOV     r0, #fsargs_WriteZeroes
        MOVS    r3, r1                  ; Dont call FS if nothing to do. VClear
        BLNE    CallFSArgs
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; EnsureFileSize
; ==============
;
; Tell the Filing System how much space this file wants

; In    r2 = required size (buffer multiple), scb^ valid

; Out   r2 = given size, scb^.allocsize updated

EnsureFileSize ENTRY "r0, r3"

 [ debugstream
 DREG r2,"Ensuring file size of "
 ]
        LDR     r3, scb_allocsize               ; Only do it if it's more than
        CMP     r2, r3                          ; we've already got allocated
        EXITS   LS                              ; Assumes VClear in entry lr

        MOV     r0, #fsargs_EnsureSize          ; r2 = new size, r3 = old extnt
        LDR     r3, scb_extent
        BL      CallFSArgs

        STRVC   r2, scb_allocsize               ; New ensured size to keep
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; WriteRealExtentOnClose
; ======================
;
; If the file is buffered and open for update, set the file's extent

; In    scb^ and streaminfo valid

WriteRealExtentOnClose ROUT

        TST     status, #scb_unbuffered :OR: scb_directory ; Can we do this ?
        BICNES  pc, lr, #V_bit

        TST     status, #scb_write      ; Should we do this ?
        BICEQS  pc, lr, #V_bit

        ENTRY   "r0, r2" ; FSArgs corrupts only r2
 [ debugstream
 DREG extent,"Setting final file extent to "
 ]
        MOV     r0, #fsargs_SetEXT      ; Write correct file length on media
        MOV     r2, extent
        BL      CallFSArgs
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; FlushBuffer
; ===========
;
; Flush a buffer to media (stream is known to be buffered).

; 'Flush' to me implies lose all knowledge of buffered data

; In    scb^, bcb^ and bufmask valid

FlushBuffer ENTRY

 [ debugstream
 LDR r14,bcb_bufferbase
 DREG r14,"Flushing buffer at base "
 ]
        BL      InvalidateBGetCache     ; Discarding buffer so can't keep cache

        LDRB    r14, bcb_status         ; Only need to flush if
 [ :LNOT: (bcb_status_bits = bcb_modified :OR: bcb_valid)
        AND     r14, r14, #bcb_modified :OR: bcb_valid
 ]
        TEQ     r14, #bcb_modified :OR: bcb_valid ; modified and valid
        BNE     %FT10

        LDR     r14, scb_status
        TST     r14, #scb_datalost
        BLNE    SetErrorDataLost
        BVS     %FT10

        BL      CallFSPutBuffer
        BVS     %FA90

10      BL      DeallocateBuffer        ; Throw away buffer. Accumulates V
        EXIT


90      Push    r1
        ADR     r1, %FT95
        BL      AppendMessageToError
        Pull    r1
        B       %BT10

95
        DCB     ": data lost", 0
        ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; FlushBuffer_2
; =============
;
; Flush contents of a buffer to media (stream is known to be buffered).

; 'Flush' to me implies lose all knowledge of buffered data

; In    scb^, bcb^ and bufmask valid

; Out   scb^, bcb^ still valid - bcb still attached to scb_bcb

FlushBuffer_2 ALTENTRY

 [ debugstream
 LDR r14,bcb_bufferbase
 DREG r14,"Flushing buffer at base "
 ]
        BL      InvalidateBGetCache     ; Discarding buffer so can't keep cache

        LDRB    r14, bcb_status         ; Only need to flush if
 [ :LNOT: (bcb_status_bits = bcb_modified :OR: bcb_valid)
        AND     r14, r14, #bcb_modified :OR: bcb_valid
 ]
        TEQ     r14, #bcb_modified :OR: bcb_valid ; modified and valid
        MOV     r14, #0                 ; Buffer contents now no longer valid
        STRB    r14, bcb_status         ; and not modified (of course !)
        MOVNE   r14, #-1                ; Buffer not in the file
        STRNE   r14, bcb_bufferbase     ; Don't think of changing this as
        EXIT    NE                      ; CallFSPutBuffer uses bcb_bufferbase!

        BL      CallFSPutBuffer
        MOV     r14, #-1
        STR     r14, bcb_bufferbase
        EXIT    VC

        B       %BA90

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; FlushStream
; ===========
;
; Flush a stream to media

; In    scb^ and streaminfo valid

; Out   VC: ok
;       VS: fail

FlushStream ENTRY "r0, r2, bcb" ; FSArgs corrupts just r2

 [ debugstream
 DREG scb,"Flushing scb ",cc
 DREG status,", status "
 ]
        TST     status, #scb_directory  ; You don't flush directories!
        EXIT    NE

        TST     status, #scb_unbuffered ; Unbuffered streams always get flushed
        BNE     %FT90

        TST     status, #scb_datalost   ; Is this stream kosher ?
        BLNE    SetErrorDataLost
        EXIT    VS

 [ debugstream
 DREG scb,"Flushing buffers on scb "
 ]
        LDR     bcb, scb_bcb            ; Is there a buffer to flush ?
        CMP     bcb, #Nowt
        BLNE    FlushBuffer

        LDR     r14, scb_fscb           ; Does Filing System require written
        LDR     r14, [r14, #fscb_info]  ; notice of flushing ?
        TST     r14, #fsinfo_flushnotify
        EXIT    EQ                      ; V from FlushBuffer

90      MOV     bcb, psr
        MOV     r0, #fsargs_Flush
        BL      CallFSArgs
        TEQVCP  bcb, #0                 ; Accumulate V
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; FlushAndCloseStream
; ===================
;
; Flush and close an scb, freeing the associated buffer space, scb and handle

; NB. Any errors coming back up from the Filing System must be ignored until
;     exit - we MUST deallocate the stream if there's any error on closing !

; In    scb^ valid, fp not necessarily valid (called on death)

; Out   VC: ok
;       VS: fail

FlushAndCloseStream ENTRY "r0-r2, $streaminfo" ; r3 in streaminfo

 [ debugstream
 Push r1
 DREG scb,"Flush and close scb ",cc
 LDR r1, scb_fscb
 ADD r1, r1, #fscb_name
 DSTRING r1," on Filing System "
 Pull r1
 ]
        ReadStreamInfo

        BL      FlushStream             ; Dump all modified buffers
                                        ; May set error 'Data lost' etc.
                                        ; Pick these up later
        BL      WriteRealExtentOnClose  ; Write correct file length on media
        BVS     %FT20

        TST     status, #scb_modified   ; Need to restamp file ?
        MOVEQ   r2, #0                  ; Both zero -> fs should't restamp
        MOVEQ   r3, #0
        BEQ     %FT50

        MOV     r0, #fsargs_ReadLoadExec
        BL      CallFSArgs              ; r2,r3 := old load,exec address
 [ debugosfind
 DREG r2,"ReadLoadExec returns ",cc
 DREG r3
 ]
20      MOVVS   r2, #0                  ; Both zero -> fs should't restamp
        MOVVS   r3, #0
        BVS     %FT50

        CMN     r2, #&00100000          ; CSet if date stamped
        LDRCC   r2, =&00000FFD          ; Stamp as type data if not dated
        MOVCS   r2, r2, LSR #8          ; Need type in bottom 12 bits
        BL      SReadTime               ; Read machine date into r2,r3

50      BL      CallFSClose             ; Close it on the Filing System

        BL      DeallocateStream        ; Get rid of handle and scb

        TEQ     fp, #0                  ; Forget errors if brain damaged
        EXIT    EQ                      ; call state

        LDR     r14, globalerror        ; Indicate error to caller
        CMP     r14, #0                 ; VClear
        SETV    NE
 [ debugstream
 EXIT VC
 DLINE "Error in FlushAndCloseStream"
 ]
        EXIT

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

        LTORG

        LNK     $fileprefix.OSFile
