title 'BDEVIF - Block DEVice Input/Output support' ; File : $BDEVIO.A86$ ; ; Description : ; ; Original Author : DIGITAL RESEARCH ; ; Last Edited By : $CALDERA$ ; ;-----------------------------------------------------------------------; ; Copyright Work of Caldera, Inc. All Rights Reserved. ; ; THIS WORK IS A COPYRIGHT WORK AND CONTAINS CONFIDENTIAL, ; PROPRIETARY AND TRADE SECRET INFORMATION OF CALDERA, INC. ; ACCESS TO THIS WORK IS RESTRICTED TO (I) CALDERA, INC. EMPLOYEES ; WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF ; THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN CALDERA, INC. WHO ; HAVE ACCEPTED THE CALDERA OPENDOS SOURCE LICENSE OR OTHER CALDERA LICENSE ; AGREEMENTS. EXCEPT UNDER THE EXPRESS TERMS OF THE CALDERA LICENSE ; AGREEMENT NO PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, ; COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, ; CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, ; TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF ; CALDERA, INC. ANY USE OR EXPLOITATION OF THIS WORK WITHOUT ; AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO CRIMINAL AND ; CIVIL LIABILITY. ;-----------------------------------------------------------------------; ; ; *** Current Edit History *** ; *** End of Current Edit History *** ; ; $Log$ ; BDEVIO.A86 1.27 94/11/30 16:25:22 ; added delayed retry for read/write to locked region ; added support for using multiple FAT copies on reads if one fails; ; BDEVIO.A86 1.26 94/02/22 17:11:25 ; Fix where corrupt dir entry results in read beyond end-of-chain (Filelink bug) ; BDEVIO.A86 1.25 93/12/15 03:07:11 ; New ddioif entry point so Int 25/26 bypasses address normalisation ; BDEVIO.A86 1.24 93/12/08 03:15:14 ; Force rebuild_ldt_root if root in JOIN's subdirectory ; BDEVIO.A86 1.23 93/11/19 18:29:29 ; Fix for SERVER print queue viewing problem ; BDEVIO.A86 1.22 93/09/21 12:43:37 ; On fdos read/write do EOF checks before SHARE LOCK checks ; BDEVIO.A86 1.21 93/09/14 20:02:50 ; Trust LFLG_PHYSICAL ; BDEVIO.A86 1.20 93/09/02 22:22:56 ; Use 32 bit sectors to read fat for build bpb if appropriate (SYQUEST bug) ; BDEVIO.A86 1.19 93/08/27 18:46:49 ; int 26 discards hash codes ; BDEVIO.A86 1.18 93/07/20 22:42:25 ; Even fewer checks on int 25/26 ; BDEVIO.A86 1.12 93/06/23 02:57:07 ; Add auto-commit to fdowrw ; BDEVIO.A86 1.11 93/05/14 13:47:41 ; Shorten media change code slightly ; BDEVIO.A86 1.9 93/03/16 22:30:21 IJACK ; UNDELETE support changes ; ENDLOG eject ! include i:mserror.equ ; F_DOS erros eject ! include i:fdos.equ eject ! include i:driver.equ eject ! include i:doshndl.def eject ! include bdos.equ eject ! include rh.equ ;***************************************************** ;* ;* bdos data area ;* ;***************************************************** PCMODE_DATA dseg word extrn current_ddsc:dword extrn current_dhndl:dword extrn current_dsk:byte ; default drive extrn current_ldt:dword ; currently selected LDT extrn dma_offset:word ; DTA offset extrn dma_segment:word ; DTA segment extrn ddsc_ptr:dword extrn err_drv:byte extrn error_dev:dword ; failing device for Int 24's extrn fdos_stub:dword extrn ioexerr:byte extrn last_drv:byte extrn ldt_ptr:dword extrn lock_bios:dword extrn phys_drv:byte extrn rwmode:byte extrn share_stub:dword extrn unlock_bios:dword extrn verify_flag:byte extrn net_retry:word BDOS_DATA dseg word extrn bcb_root:dword extrn deblock_seg:word extrn fdos_hds_drv:byte extrn fdos_hds_blk:word extrn fdos_hds_root:word extrn fdos_ret:word public adrive public clsize public dosfat public cur_dma public cur_dma_seg public datadd public diradd public dirinroot public dirperclu public fatadd public hdsaddr public lastcl public logical_drv public mult_sec public nfatrecs public nfats public pblock public physical_drv public psecsiz public req_hdr public secperclu eject ; The following specify the drive selected for the current operation hdsaddr dw 0 ; current HDS address (0 means at root) logical_drv db 0 ; logical drive number physical_drv db 0 ; physical disk number ; The following describe the currently active drive - not this may differ from ; the currently selected drive above due to eg. flushing dirty buffers ; Local copy of DDSC_ variables - ORDER CRITICAL - must match DDSC_ local_ddsc rb 0 psecsiz dw 0 ; byte size of sector clmsk db 0 clshf db 0 fatadd dw 0 ; sector offset of 1st FAT sector byte_nfats db 0 ; number of FAT's dirinroot dw 0 ; # dir entries in root datadd dw 0 ; sector offset of data sector lastcl dw 0 ; # last cluster (after adjustment) if DOS5 dw 0 ; # sectors per FAT else db 0 ; # sectors per FAT (nb. may be inaccurate on large drives) endif diradd dw 0 ; sector offset of 1st root DIR sector LOCAL_DDSC_LEN equ offset $ - offset local_ddsc ; some extra parameters calculated from local_ddsc for convenience nfats dw 0 ; # FAT's (WORD is handier) nfatrecs dw 0 ; # sectors per FAT (accurate version) clsize dw 0 ; cluster size in bytes secperclu dw 0 ; # sectors per cluster dirperclu dw 0 ; # dir enrties in subdir dosfat dw 0 ; FAT length indicator (FAT12 or FAT16) ; The following specify the next block read/write operation on the active drive adrive db 0ffh ; currently active disk pblock dw 0, 0 ; absolute block address mult_sec dw 1 ; multi sector count passed to xios cur_dma dw 0 cur_dma_seg dw 0 fdrwreq dw 0 ; requested count (roundup) public fdrwflg fdrwflg db 0 ; bdosrw flags fdrwcnt dw 0 ; requested byte count for read/write fdrwptr rd 0 ; disk transfer address for read/write fdrwoff dw 0 ; offset for R/W DTA fdrwseg dw 0 ; segment for R/W DTA fdrwsec rd 1 ; physical block for fdosrw fdrwsecoff dw 0 ; offset within sector fdrwdircnt dw 0 ; # sectors in direct xfer byteoff dw 0 ; fdosrw local variable dw 0 ; byte offset with file blk dw 0 ; current cluster of filepos blkidx dw 0 ; current cluster index within file blkoffset dw 0 ; offset within cluster ; static request header for DOS device driver I/O Public req_hdr req_hdr rb 0 req_len db 22 req_unit rb 1 req_cmd rb 1 req_status rw 1 req_rwmode db 0 ; action hint for device drivers rb 7 req_media rb 1 rb 16 req1_return equ byte ptr req_media+1 req1_volid equ word ptr req_media+2 req2_buffer equ word ptr req_media+1 req2_bpb equ word ptr req_media+5 req3_buffer equ word ptr req_media+1 req3_count equ word ptr req_media+5 req3_sector equ word ptr req_media+7 req3_volid equ word ptr req_media+9 req4_buffer equ word ptr req_media+1 req4_count equ word ptr req_media+5 req4_sector equ word ptr req_media+7 req4_volid equ dword ptr req_media+9 req4_bigsector equ dword ptr req_media+13 eject BDOS_CODE cseg extrn alloc_chain:near extrn bpb2ddsc:near ; converts BPB to DDSC extrn buffers_check:near ; look for buffers extrn delfat:near extrn discard_all:near ; discard all buffers extrn discard_dir:near ; discard directory buffers extrn discard_dirty:near ; discard all dirty buffers extrn discard_files:near ; discard open files extrn fdos_error:near extrn fdos_restart:near extrn file_update:near extrn fixfat:near extrn getnblk:near ; get block value from FAT extrn get_ldt:near extrn get_ldt_raw:near extrn hdsblk:near ; get current HDS block extrn hshdscrd:near extrn locate_buffer:near extrn rebuild_ldt_root:near extrn timestamp_dhndl:near extrn update_dat:near extrn update_fat:near extrn share_delay:near public block_device_driver public clus2sec public device_driver public read_block public select_adrive public select_logical_drv public select_physical_drv public write_block eject eject Public get_ddsc get_ddsc: ;-------- ; On Entry: ; AL = physical drive ; On Exit: ; CY set if bad drive, else ; ES:BX -> DDSC_ ; (All other registers preserved) ; cmp al,ss:phys_drv jae get_ddsc30 les bx,ss:ddsc_ptr get_ddsc10: cmp bx,0FFFFh ; end of the line je get_ddsc30 cmp al,es:DDSC_UNIT[bx] ; does the unit match ? je get_ddsc20 ; no, try the next les bx,es:DDSC_LINK[bx] jmps get_ddsc10 get_ddsc20: ; clc ret get_ddsc30: stc ret eject ; Read/Write from/to disk file ; entry: CURRENT_DNHDL -> file handle ; BDRWFLG = 1 => read ; 0 => write ; ES:DI = buffer (32 bit: off/seg) ; CX = requested byte count (16 bit) ; exit: FDOS_RET = number of bytes read/written ; CURRENT_DHNDL incremented by FDOS_RET public fdosrw ; read/write to/from disk file fdosrw: ;------ call fdrw_prepare ; set up address, where we are in file jc fdrw_error ; stop if we have a problem call fdrw_size ; extend file if necessary jc fdrw_error ; bail out if we can't cmp fdrwcnt,0 ; are we truncating? jne fdrw_loop ; read/write if non-zero count test fdrwflg,1 ; writing zero bytes? jnz fdrw_error ; (reading has no meaning) call fdw_trunc ; writing truncates the file jmps fdrw_nobigger fdrw_error: ret fdrw_loop: ; loop here for long reads/writes call fdrw_seek ; seek to position for xfer jc fdrw_exit ; should get error's now... jnz fdrw_buffered ; deblocking required if not aligned mov cx,fdrwcnt ; CX = requested transfer size cmp cx,psecsiz ; at least one sector transferred? jb fdrw_buffered ; if less, need deblocked transfer mov fdrwreq,cx ; requested count for direct r/w call direct_rw ; transfer straight to/from TPA jmps fdrw_more fdrw_buffered: ; perform deblocked read/write call deblock_rw ; transfer via BDOS buffer fdrw_more: add fdrwoff,ax ; adjust buffer address add fdos_ret,ax ; adjust return code add byteoff,ax ; adjust file offset adc byteoff+2,0 sub fdrwcnt,ax ; adjust remaining count ja fdrw_loop ; still more to do fdrw_exit: les bx,current_dhndl mov ax,fdos_ret ; get total xfered and update position add es:DHNDL_POSLO[bx],ax adc es:DHNDL_POSHI[bx],0 test fdrwflg,1 jnz fdrw_return ; skip if reading mov ax,byteoff ; has the file grown ? mov dx,byteoff+WORD sub ax,es:DHNDL_SIZELO[bx] sbb dx,es:DHNDL_SIZEHI[bx] jb fdrw_nobigger ; yes, update the file size add es:DHNDL_SIZELO[bx],ax adc es:DHNDL_SIZEHI[bx],dx fdrw_nobigger: call timestamp_dhndl ; record the current time test es:DHNDL_MODE[bx],DHM_COMMIT jz fdrw_return ; is auto-commit in place ? call file_update ; yes, commit the file fdrw_return: ret fdw_trunc: ;--------- ; On Entry: ; BLKIDX = block number within file ; BLKOFFSET = block offset ; On Exit: ; DHNDL_SIZE adjusted, any excess clusters freed ; les bx,current_dhndl mov cx,blkoffset ; get offset within current block mov ax,blkidx ; get logical block number jcxz fdw_t10 ; skip if no data in last block inc ax ; else add in another cluster fdw_t10: ; AX = # of clusters required in file test ax,ax jnz fdw_t20 xchg ax,es:DHNDL_BLK1[bx] ; forget about chain jmps fdw_t50 fdw_t20: xchg ax,cx ; CX = # of blocks to keep mov ax,es:DHNDL_BLK1[bx] ; get first block in file fdw_t30: ; scan all block we want to keep push cx push ax call getnblk ; get next block pop bx pop cx cmp ax,lastcl ; stop on premature end of chain ja fdw_t60 loop fdw_t30 push ax ; yep, remember what mov ax,dosfat xchg ax,bx ; truncate chain at cluster AX call fixfat ; as thats all we need pop ax fdw_t50: call delfat ; release the chain fdw_t60: les bx,current_dhndl mov ax,byteoff ; now truncate the file mov es:DHNDL_SIZELO[bx],ax mov ax,byteoff+2 mov es:DHNDL_SIZEHI[bx],ax xor ax,ax ; cause reads/writes to scan mov es:DHNDL_BLK[bx],ax ; block chain from start mov es:DHNDL_IDX[bx],ax mov fdos_ret,ax ; no logical errors ret fdrw_prepare: ;------------ ; Normalise the xfer address and count ; Calculate current position in the file ; ; On Entry: ; ES:DI -> buffer ; CX = bytes to xfer ; On Exit: ; FDRWSEG:FDRWOFF -> normalised buffer ; FDRWCNT = bytes to xfer ; FDOS_RET = bytes xfer'd (0) ; PREREAD = TRUE ; BYTEOFF = current offset in file ; BLKIDX = cluster containing current file position ; BLKOFFSET = offset within cluster ; CY set if current position theoretically impossible ; xor ax,ax ; AX = 0 mov fdos_ret,ax ; initialize byte return count mov fdrwcnt,cx ; save byte count for read/write mov ax,000Fh and ax,di ; get offset within paragraph mov fdrwoff,ax ; save normalized offset for read/write add ax,cx ; do we overflow 64k ? jnc fdrw_p10 ; yes, then forget about what would sub fdrwcnt,ax ; overflow this segment fdrw_p10: mov cl,4 shr di,cl ; DI = paragraph offset mov ax,es add ax,di ; AX = effective segment jnc fdrw_p20 ; if above 1 MByte base it at FFFF inc ax ; AX = para's above FFFF shl ax,cl ; make it bytes add fdrwoff,ax ; add to offset mov ax,0ffffh ; use our magic segment fdrw_p20: mov fdrwseg,ax ; save normalized segment for read/write les bx,current_dhndl mov ax,es:DHNDL_POSLO[bx] mov byteoff,ax ; copy position to local variables mov ax,es:DHNDL_POSHI[bx] mov byteoff+WORD,ax mov cx,clsize mov ax,lastcl mul cx ; DX:AX = maximum size of disk sub ax,byteoff sbb dx,byteoff+WORD ; beyond this we can't go jc fdrw_p30 mov ax,byteoff ; DX:AX = current file size mov dx,byteoff+WORD div clsize mov blkidx,ax ; save it for later mov blkoffset,dx ; DX = offset within cluster clc ; theoretically possible fdrw_p30: ret fdrw_size: ;--------- ; On reads check xfer starts within file, and clip size to reflect EOF. ; On writes try to extend to cluster chain so it is big enough to contain ; the data we wish to write. ; ; On Entry: ; BYTEOFF = current position in file ; FDRWCNT = extra bytes requested ; On Exit: ; FDRWCNT adjusted if read past EOF ; CY set if problem extending file ; les bx,current_dhndl mov ax,es:DHNDL_SIZELO[bx] ; are we past the end of file mov dx,es:DHNDL_SIZEHI[bx] ; if so we may wish to extend on write sub ax,byteoff ; AX,DX = current offset sbb dx,byteoff+WORD ; are we already beyond EOF ? jb fdrw_s40 sub ax,fdrwcnt ; will we be going beyond EOF ? sbb dx,0 jnb fdrw_s10 ; no, whole xfer is OK test fdrwflg,1 ; check if we're reading jz fdrw_s50 ; if we are just adjust the add fdrwcnt,ax ; amount we can xfer fdrw_s10: ; We call share concerning the XFER to check if any of the proposed ; file region is locked. ; les bx,current_dhndl ; check for locked regions mov cx,net_retry fdrw_s15: push cx mov cx,fdrwcnt ; in the file callf share_stub+S_FDOSRW pop cx jnc fdrw_s20 ; CY set on error dec cx jz fdrw_s30 call share_delay jmps fdrw_s15 fdrw_s20: ret fdrw_s30: jmp fdos_error ; CY clear, AX = error code fdrw_s40: ; We are going beyond EOF - if it is a read we fail it, if a write ; try to extend the file test fdrwflg,1 ; check if we're reading stc ; assume failure jnz fdrw_s20 ; reads fail now, writes extend file fdrw_s50: call fdrw_s10 ; make sure SHARE doesn't object ; jmp fdwrite_extend ; if not try to extend the file fdwrite_extend: ;-------------- ; Try to extend to file to the required size before we write to it ; On Entry: ; ES:BX -> DHNDL_ ; BYTEOFF = current position in file ; FDRWCNT = extra requested ; On Exit: ; CY clear if cluster chain now big enough for desired file size ; mov ax,byteoff ; AX,DX = current offset mov dx,byteoff+2 add ax,fdrwcnt ; AX,DX = offset after r/w if success adc dx,0 ; add offset from lower 16 bits div clsize ; AX whole blocks required test dx,dx ; any remainder ? jz fdw_e05 ; yes, we have a partial block inc ax ; round up blocks required fdw_e05: xchg ax,cx ; CX blocks are required mov ax,es:DHNDL_BLK1[bx] ; assume we need to follow from start test ax,ax jz fdw_e30 ; if no starting block do the lot dec cx ; else count # extra blocks required mov dx,es:DHNDL_BLK[bx] ; do we have a current block ? test dx,dx ; if not we have to start jz fdw_e10 ; with the first block mov ax,dx ; new starting block as this must sub cx,es:DHNDL_IDX[bx] ; be less than extended size fdw_e10: jcxz fdw_e20 ; bail out of we have enough fdw_e15: push ax ; save current block push cx ; save # required call getnblk ; AX = next block in chain pop cx ; restore # required pop bx ; recover previous block cmp ax,lastcl ; end of chain yet ? ja fdw_e40 loop fdw_e15 ; try another one fdw_e20: clc ; chain is already long enough ret fdw_e30: ; We have no initial block, so allocate them all ; xor ax,ax ; no preconceptions over where we call alloc_chain ; allocate chain of CX clusters jc fdw_e35 les bx,current_dhndl mov es:DHNDL_BLK1[bx],ax ; remember initial block clc fdw_e35: ret fdw_e40: ; We have a partial chain, ending at cluster BX push bx ; save current end of chain xchg ax,bx ; start allocating from cluster AX a call alloc_chain ; a chain of CX clusters pop bx jc fdw_e45 xchg ax,bx ; AX = previous cluster, link cluster call fixfat ; BX to end of the chain clc fdw_e45: ret fdrw_seek: ;--------- ; On Entry: ; BYTEOFF = offset within file ; On Exit: ; BLK = cluster containing current filepos ; BLKOFFSET = offset within cluster ; BLKIDX = cluster index within file ; PBLOCK = sector containing current filepos ; POFFSET = offset within sector (reflected in ZF) ; mov ax,byteoff ; where are we now ? mov dx,byteoff+WORD div clsize mov blkidx,ax ; save cluster mov blkoffset,dx ; and offset within it les bx,current_dhndl cmp ax,es:DHNDL_IDX[bx] ; do we know this block ? jb fdrw_seek10 ; we can't go backwards, use 1st block mov cx,es:DHNDL_BLK[bx] ; get last index block jcxz fdrw_seek10 ; use 1st block if it isn't valid sub ax,es:DHNDL_IDX[bx] ; skip this many jmps fdrw_seek20 fdrw_seek10: mov cx,es:DHNDL_BLK1[bx] ; start with 1st block fdrw_seek20: xchg ax,cx ; AX = starting cluster jcxz fdrw_seek40 ; CX = clusters to skip fdrw_seek30: push cx call getnblk ; get next block pop cx cmp ax,lastcl ; stop on premature end of chain ja fdrw_seek_error ; (file size must be wrong..) loop fdrw_seek30 fdrw_seek40: les bx,current_dhndl mov dx,blkidx mov es:DHNDL_IDX[bx],dx ; remember this position for next time mov es:DHNDL_BLK[bx],ax mov blk,ax ; save the block for coniguous checks mov bx,blkoffset call clus2sec ; convert to sector/offset mov word ptr fdrwsec,ax ; remember this block mov word ptr fdrwsec+WORD,dx mov fdrwsecoff,bx ; and offset within it test bx,bx ; set ZF ; clc ; no problems ret fdrw_seek_error: stc ; we hit unexpected end of chain ret ; (shouldn't happen) ; Read/write partial sector via deblocking code ; On Entry: ; FDRWSEC = sector address on disk ; FDRWSECOFF = offset within sector ; FDRWCNT = byte count for read/write ; On Exit: ; AX = # of bytes transferred deblock_rw: ;---------- mov cx,0FF00h+BF_ISDAT ; CH = preread, buffer is data mov dx,word ptr fdrwsec ; set sector to xfer from mov ah,byte ptr fdrwsec+WORD call locate_buffer ; ES:SI -> buffer mov bx,fdrwsecoff ; BX = offset within sector mov ax,psecsiz mov dx,ax ; DX = physical sector size sub ax,bx ; AX = bytes left in sector cmp ax,fdrwcnt ; more than we want to transfer? jb deblkrw10 ; yes, only do up to end of sector mov ax,fdrwcnt ; else do up to end of request deblkrw10: mov cx,ax ; AX, CX = byte count ; (AX for return, CX for MOVSW) push ds test fdrwflg,1 ; check if reading or writing jz dblkrw30 ; skip if writing push es les di,fdrwptr ; destination is user memory pop ds ; source segment is data buffer lea si,BCB_DATA[si+bx] ; DS:SI -> data jmps dblkrw40 ; copy the data dblkrw30: ; we're writing or es:BCB_FLAGS[si],BF_DIRTY; mark buffer as dirty lea di,BCB_DATA[si+bx] ; ES:DI -> data lds si,fdrwptr ; source is user memory dblkrw40: shr cx,1 ; make it a word count rep movsw ; move the words jnc dblkrw50 ; skip if even # of bytes movsb ; else move last byte dblkrw50: pop ds ; restore registers ret ; entry: BYTEOFF = 32-bit offset into file ; BLKOFFSET = byte offset within cluster ; PRVBLK = block in which transfer starts ; FDRWREQ = requested transfer length ;--------- direct_rw: ;--------- sub dx,dx ; assume no extra blocks required mov ax,fdrwreq ; total byte count mov cx,clsize ; get number of bytes sub cx,blkoffset ; CX = bytes remaining in this block sub ax,cx ; if wholly containined within block jbe direct_rw10 ; then leave it alone div clsize ; else get # of extra clusters xchg ax,dx ; DX = clusters, AX = remainder or ax,ax ; round up if any remainder jz direct_rw10 ; skip if even number inc dx ; else one more cluster direct_rw10: ; DX = # of contiguous clusters req'd call check_cont ; check how many contiguous blocks mov ax,clsize ; space = cnt * dpbptr->clsize; mul cx ; AX:DX = # of bytes transferrable sub ax,blkoffset ; BX = skipped bytes in 1st cluster sbb dx,0 ; AX:DX = max # of bytes transferrable ; from current position test dx,dx jnz direct_rw20 ; if > 64 K, use up request cmp ax,fdrwreq ; if less than we requested jb direct_rw30 ; then lets do it direct_rw20: xor dx,dx mov ax,fdrwreq ; else use requested count direct_rw30: div psecsiz ; AX = # complete sectors mov fdrwdircnt,ax ; save direct sector count mov mult_sec,ax ; set multi sector count mul psecsiz ; AX = bytes to xfer push ax ; save for later mov ax,fdrwoff ; FDRWPTR = disk transfer address mov cur_dma,ax mov ax,fdrwseg mov cur_dma_seg,ax mov ax,word ptr fdrwsec ; set sector to xfer from mov word ptr pblock,ax mov ax,word ptr fdrwsec+WORD mov word ptr pblock+WORD,ax mov rwmode,0000$0110b ;data read/write mov cl,fdrwflg and cl,1 ; CL = read/write flag jz direct_rw40 xor cx,cx ; indicate no retries call read_block ; read in the data jmps direct_rw50 direct_rw40: call write_block ; write out the data direct_rw50: call SynchroniseBuffers ; synchronize BCBs with direct transfer pop ax ; recover bytes xfered push ds ! pop es ; restore ES = SYSDAT ret check_cont: ; check for adjacent blocks or space ;---------- ; entry: DX = # of extra contiguous blocks req'd ; exit: CX = # of contiguous blocks available ; We first check all adjacent allocated clusters. ; If we'd like more and we find the end of file ; and we are writing and the adjacent blocks aren't ; allocated, then we count them as well and link ; them into the file. mov ax,blk ; current block number xor cx,cx ; contiguous blocks found = 0 test dx,dx ; any extra required ? jz check_cont20 check_cont10: ; get link of current block push ax ; save current block push cx ; save extra blocks so far push dx ; save extra blocks we'd like call getnblk ; get the link pop dx pop cx pop bx inc bx ; BX = current block + 1 cmp ax,bx ; check if next block is contiguous jne check_cont20 ; and try for another inc cx ; extra contiguous cluster dec dx ; one less block to check jnz check_cont10 ; try again if we still want more check_cont20: ; we can do CX extra clusters inc cx ; include 1st cluster too.. ret ;------------------ SynchroniseBuffers: ; synchronize BCBs after multi sector transfer ;------------------ ; On Entry: ; FDRWSEG:FDRWOFF = transfer address for IO_READ/IO_WRITE ; FDRWDIRCNT = physical sector count for direct transfer ; FDRWSEC = sector address for transfer ; FDWRFLG = even for write, odd for read ; On Exit: ; direct transfer buffer or BCB updated if BCB overlap ; ; If any data buffer is found, that falls into the region affected ; by the direct sector transfer, the following action is performed: ; If the operation was a read and the sector buffer is clean, ; no action is required. If it was dirty, the buffer contents is ; copied to the corresponding location in the DTA buffer. ; If the operation was a write, the sector buffer is discarded. ; ; mov dx,word ptr fdrwsec mov ah,byte ptr fdrwsec+WORD mov al,adrive ; get our drive number lds bx,bcb_root ; DS:BX -> 1st buffer SynchroniseBuffers10: test ds:BCB_FLAGS[bx],BF_ISDAT; is this a data buffer? jz SynchroniseBuffers30 ; skip if directory or FAT cmp al,ds:BCB_DRV[bx] ; does the drive match? jne SynchroniseBuffers30 ; skip if different mov si,ds:BCB_REC[bx] ; compute bcb->rec - prec sub si,dx ; result in SI,CL (lsb..msb) mov cl,ds:BCB_REC2[bx] sbb cl,ah ; get bits 16-23 of result jne SynchroniseBuffers30 ; skip if bcb->rec < prec cmp si,ss:fdrwdircnt ; else check against transfer length jae SynchroniseBuffers30 ; skip if beyond transfer length test ss:fdrwflg,1 ; test direction: read or write jz SynchroniseBuffers20 ; skip if disk write test ds:BCB_FLAGS[bx],BF_DIRTY; if buffer dirty, did read old data jz SynchroniseBuffers30 ; else data read was valid push ax ! push dx ; save record address mov ax,ss:psecsiz ; # of bytes in sector buffer mov cx,ax shr cx,1 ; CX = words per sector mul si ; AX = byte offset from start buffer add ax,ss:fdrwoff ; AX = offset xchg ax,di ; DI = offset mov es,ss:fdrwseg ; ES:DI -> data to be replaced lea si,BCB_DATA[bx] rep movsw ; move CX words (one physical sector) pop dx ! pop ax ; restore record address jmps SynchroniseBuffers30 SynchroniseBuffers20: ; multi sector write mov ds:BCB_DRV[bx],0FFh ; discard this sector SynchroniseBuffers30: if DOS5 mov bx,ds:BCB_NEXT[bx] cmp bx,ss:word ptr bcb_root else lds bx,ds:BCB_NEXT[bx] ; get next buffer address cmp bx,0ffffh endif jne SynchroniseBuffers10 ; if so stop push ss ! pop ds ; restore DS ret eject Public blockif, ddioif ;======= ================================ blockif: ; disk read/write bios interface ;======= ================================ ; entry: AL = BIOS Request function number ; ADRIVE = block device to xfer to/from ; RWMODE = read/write mode ; CUR_DMA_SEG:CUR_DMA -> xfer address ; PBLOCK = starting block of xfer ; MULT_CNT = # blocks to xfer ; exit: AX = BX = output mov req_cmd,al mov al,rwmode ; copy rwmode to where the device mov req_rwmode,al ; driver can get the hint mov ax,cur_dma ; get DMA offset push ax ; (save it) and ax,000Fh ; get offset within paragraph mov req4_buffer,ax ; set transfer offset pop ax ; (restore offset) mov cl,4 shr ax,cl ; convert to paragraphs add ax,cur_dma_seg ; add in the segment mov req4_buffer+2,ax ; set transfer segment mov ax,mult_sec ; get requested sector count mov req4_count,ax ; set requested sector count ;------ ddioif: ;------ push es mov al,adrive ; get selected drive call get_ddsc ; ES:BX -> DDSC mov ax,word ptr pblock mov dx,word ptr pblock+WORD ; DX:AX = starting block push es les si,es:DDSC_DEVHEAD[bx] ; ES:SI -> device driver if DOS5 ; DOS 4 support mov word ptr req4_bigsector,ax mov word ptr req4_bigsector+2,dx mov req_len,RH4_LEN ; set length of request header test es:DH_ATTRIB[si],DA_BIGDRV ; large sector number support? jz blockif10 ; no, normal request header mov ax,-1 ; indicate we use 32-bit sector number blockif10: mov req4_sector,ax ; set requested sector address else mov word ptr req4_bigsector,ax mov word ptr req4_bigsector+2,dx mov req4_sector,ax ; set requested sector address mov req4_sector+2,dx ; (support large DOS drives) mov req_len,RH4_LEN ; assume 22 bytes in request header test es:DH_ATTRIB[si],DA_BIGDRV ; large sector number support? jz blockif10 ; no, normal request header mov req_len,RH4_LEN+2 ; else indicate long request blockif10: endif pop es call block_device_driver ; make call to device driver js blockif20 xor ax,ax ; no error blockif20: mov mult_sec,1 ; reset sector count mov bx,ax ; AX, BX = return code pop es ret block_device_driver: ;------------------ ; entry: ES:BX -> DDSC, req_hdr partly filled in ; exit: AX = status after function ; SF = 1 if error occurred ; note: BX preserved mov al,es:DDSC_MEDIA[bx] mov req_media,al ; set current media byte mov al,es:DDSC_RUNIT[bx] ; get relative unit # mov req_unit,al ; set the unit push ds push es push bx push ds lds si,es:DDSC_DEVHEAD[bx] pop es mov bx,offset req_hdr ; ES:BX -> request packet call device_driver ; do operation pop bx pop es pop ds ret ; On Entry: ; DS:SI Device Header ; ES:BX Current Request Header ; ; On Exit: ; AX Request Header Status ; device_driver: ;------------ xor ax,ax mov es:RH_STATUS[bx],ax ; Initialise return status push es push bx push bp callf ss:lock_bios ; lock access to BIOS push cs call device_driver10 ; fake a callf callf ss:unlock_bios ; unlock access to BIOS pop bp pop bx pop es sti cld ; Restore Flags mov ax,es:RH_STATUS[bx] ; Return the Status to the caller test ax,ax ; set SF=1 if error ret device_driver10: push ds push ds:DH_INTERRUPT[si] ; interrupt routine address on stack push ds push ds:DH_STRATEGY[si] ; strategy routine address on stack retf ; retf to strategy, interrupt, us eject ; Select drive and check for door open ints ; Build fdos_hds to refer to the drive ; Exit: DL = drive to be selected (0-15) select_logical_drv: ;------------------ ; On Entry: ; AL = logical drive to select (with change media checks) ; On Exit: ; ES:BX -> LDT_ ; cmp al,last_drv ; is it a legal drive ? jae select_drv_bad ; no, reject it now mov logical_drv,al ; save logical drive call get_ldt ; ES:BX -> LDT_ for drive jc select_physical_drv ; no LDT_ during init, must be physical mov word ptr current_ldt,bx mov word ptr current_ldt+WORD,es mov al,es:byte ptr LDT_FLAGS+1[bx] ; is the drive valid ? test al,(LFLG_NETWRKD+LFLG_JOINED)/100h jnz select_drv_bad ; reject networked/joined drives test al,LFLG_PHYSICAL/100h jz select_drv_bad ; reject non-physical drives mov al,es:LDT_NAME[bx] ; get the drive from the ascii name and al,1fh ; as the drive may require rebuilding dec ax ; make it zero based push es ! push bx call select_physical_drv ; select the physical root pop bx ! pop es cmp es:LDT_ROOTLEN[bx],2 ; if logical and physical roots jbe select_logical_drv30 ; are the same we are OK now if JOIN mov al,es:LDT_DRV[bx] ; should we be on a different cmp al,fdos_hds_drv ; physical drive ? jne select_logical_drv10 ; if so then we'd better rebuild endif cmp es:LDT_BLK[bx],0FFFFh ; did we have a media change ? jne select_logical_drv20 ; then we'd better rebuild select_logical_drv10: call rebuild_ldt_root ; the LDT_ root block select_logical_drv20: mov ax,es:LDT_ROOT[bx] ; get virtual root from LDT mov fdos_hds_root,ax ; move there mov fdos_hds_blk,ax if JOIN mov al,es:LDT_DRV[bx] ; same with drive mov fdos_hds_drv,al endif select_logical_drv30: ret select_physical_drv: ;------------------- ; On Entry: ; AL = physical drive to select (with change media checks) ; On Exit: ; None ; xor dx,dx mov fdos_hds_blk,dx ; put it in the root by default mov fdos_hds_root,dx mov fdos_hds_drv,al ; set physical drive in working HDS cmp al,phys_drv ; should we have a DDSC_ for this drive jae select_drv_bad ; no, we can't select it then mov physical_drv,al ; save physical drive number call select_adrive ; no, better select it jc select_drv_critical_error ret select_drv_bad: ;-------------- ; An attempt has been made to select a bad drive, ; return a logical error "invalid drive" mov ax,ED_DRIVE ; ED_DRIVE "invalid drive" jmp fdos_error select_drv_critical_error: ;------------------------- ; The drive is logically correct, so all error at this point must ; be physical ones - so we want a critical error jmp generate_critical_error eject select_adrive: ;------------- ; This entry is called to physically select a drive (eg. when flushing buffers) ; It does not alter the current physical_drv setting, which must be re-selected ; afterwards by the caller. ; ; On Entry: ; AL = disk to select (range validated) ; On Exit: ; CY set if a problem selecting the drive mov adrive,al mov err_drv,al ; save error drive call get_ddsc ; ES:BX -> DDSC_ for drive mov al,1 ; AL = "Unknown Unit" jc select_drv_err ; error if no DDSC_ mov ax,es:word ptr DDSC_DEVHEAD[bx] mov word ptr error_dev+0,ax mov ax,es:word ptr DDSC_DEVHEAD+2[bx] mov word ptr error_dev+2,ax push es ; remember driver address for error's push bx ; preserve DDSC_ call check_media ; see if media has changed pop bx ; restore DDSC_ pop es jc select_drv_err ; select the disk drive and fill the drive specific variables ; entry: ES:BX -> DDSC_ of disk to select ; AX <> 0 if drive requires BPB rebuilt ; exit: CY flag set on error test ax,ax ; device driver, new select? jz select_ddsc ; use current DDSC if old select call build_ddsc_from_bpb ; else get BPB and build new DDSC jc select_drv_err ; carry flag reset call select_ddsc ; use to DDSC for select if DELWATCH mov ah,DELW_NEWDISK ; we have a new disk so I guess mov al,physical_drv ; I'd better tell delwatch les bx,current_ddsc ; about the new disk so it callf fdos_stub ; knows to update itself endif clc ;select disk function ok ret select_drv_err: ; On Entry: ; AL = extended error code ; CY set ; mov ioexerr,al ; save error code ret select_ddsc: ;----------- ; On Entry: ; ES:BX -> DDSC_ of drive to be selected mov word ptr current_ddsc,bx mov word ptr current_ddsc+WORD,es push ds ! push es pop ds ! pop es ; swap ES and DS lea si,DDSC_SECSIZE[bx] ; DS:SI -> DDSC_ original mov di,offset local_ddsc ; ES:DI -> DDSC_ copy mov cx,LOCAL_DDSC_LEN rep movsb ; make a local copy of interesting bits push es ! pop ds ; DS=ES=local data segment mov ax,psecsiz ; now initialise some other vaiiables mov cl,clshf shl ax,cl ; AX = bytes per cluster mov clsize,ax xor ax,ax mov al,clmsk inc ax ; AX = sectors per cluster mov secperclu,ax mov al,byte_nfats ; AX = number of FATs mov nfats,ax ; (it's handier as a word mov ax,diradd ; number of FAT records can be sub ax,fatadd ; bigger than 255 xor dx,dx div nfats mov nfatrecs,ax mov cx,FCBLEN mov ax,clsize ; convert from cluster size xor dx,dx ; to number of dir entries div cx ; per cluster - handy for mov dirperclu,ax ; subdirectories mov ax,FAT12 cmp lastcl,MAX12 ; is it a 12 bit FAT ? jbe select_ddsc10 mov ax,FAT16 ; no, it's 16 bit select_ddsc10: mov dosfat,ax ; remember which for later clc ; drive all selected ret eject build_ddsc_from_bpb: ; call device driver to build BPB, convert to DDSC_ ;------------------- ; On Entry: ; ES:BX -> DDSC_ to rebuild ; On Exit: ; ES:BX preserved ; CY set on error ; AL = error code push es push bx ; save DDSC_ address xor di,di mov ax,deblock_seg mov es,ax ; ES:DI -> deblock seg test ax,ax ; if we are deblocking spare buffer jnz build_bpb10 ; might be in high memory dec ax ; AX = FFFF mov dx,ax ; compute impossible record # mov cx,BF_ISDIR ; locate directory sector w/o preread call locate_buffer ; this will find the cheapest buffer mov es:BCB_DRV[si],0FFh ; don't really want this... lea di,BCB_DATA[si] ; ES:DI -> disk buffer build_bpb10: mov req4_buffer,di ; xfer to ES:DI mov req4_buffer+2,es pop bx ; restore DDSC_ address pop es push ds lds si,es:DDSC_DEVHEAD[bx] ; DS:SI -> device header mov ax,ds:DH_ATTRIB[si] ; non-FAT ID driver ("non-IBM") bit pop ds ; in device header attributes test ax,DA_NONIBM jnz bldbpb30 ; skip if media byte in FAT not used mov req_rwmode,0 ; read of system area mov req_len,RH4_LEN ; set length field mov req_cmd,CMD_INPUT ; read first FAT sector off disk if DOS5 test ax,DA_BIGDRV ; large sector numbers ? endif mov ax,1 mov req4_count,ax ; read 1st FAT sector cwd ; DS:AX = sector 1 mov word ptr req4_bigsector,ax mov word ptr req4_bigsector+2,dx if DOS5 jz bldbpb20 dec ax ! dec ax ; AX = 0FFFFh bldbpb20: endif mov req4_sector,ax ; set requested sector address mov req4_sector+2,dx ; (support large DOS drives) call block_device_driver ; try to read FAT sector, AX = status js bldbpb_err ; skip if errors (AX negative) bldbpb30: mov req_len,RH2_LEN ; length of req mov req_cmd,CMD_BUILD_BPB ; "build bpb" call block_device_driver ; call the device driver js bldbpb_err ; skip if errors (AX negative) push ds push es push bx mov di,bx ; ES:DI -> DDSC_ to initialise lds si,dword ptr req2_bpb ; DS:SI -> BPB to convert call bpb2ddsc ; rebuild the DDSC_ pop bx pop es pop ds clc ; success - we have a new DDSC_ ret bldbpb_err: stc ; we had a problem ret eject ;----------- check_media: ; check media if DPH media flag set ;----------- ; On Entry: ; ES:BX -> DDSC_ of physical drive to check ; On Exit: ; CY set on error, AX = error code ; else ; AX <> 0 if disk requires BPB rebuild ; If definite/possible change then LDT's marked as invalid ; If possible then buffers/hashing discarded provided they are clean ; If definite then all buffers/hashing for drive discarded even if dirty ; mov req_len,RH1_LEN ; set length field mov req_cmd,CMD_MEDIA_CHECK ; media check routine call block_device_driver ; call the device driver jns chkmed10 stc ; we have a problem, generate ret ; an error chkmed10: mov al,req_media+1 ; else get returned value xor ah,ah ; watch out for 1st access too.. xchg ah,es:DDSC_FIRST[bx] ; treat never accessed as changed cmp al,1 ; 1 = no change jne chkmed20 dec ax ; AL=0, build bpb only if DDSC_FIRST ; clc ; it all went OK ret chkmed20: mov dl,adrive ; media may have/has changed call mark_ldt_unsure ; so force LDT's to unsure ; AL = 00 if maybe changed, FF for definitely changed test al,al jz chkmed_maybe ; media may have changed chkmed_changed: ; disk has changed for sure call discard_files ; discard open files jmps chkmed30 ; discard buffers, build bpb required chkmed_maybe: ; disk has possibly changed call discard_dir ; we can always discard dir as they mov ah,BF_DIRTY ; won't be dirty mov al,adrive call buffers_check ; any dirty buffers on adrive? jnz chkmed40 ; yes, can't discard FAT chkmed30: call discard_all ; discard buffers for drive chkmed40: or ax,0FFFFh ; better rebuild bpb ; clc ret Public mark_ldt_unsure mark_ldt_unsure: ;--------------- ; On Entry: ; DL = physical drive ; On Exit: ; All corresponding LDT's marked as unsure ; All reg preserved ; push es push ax push bx xor ax,ax ; start with drive A: mlu10: call get_ldt_raw ; ES:BX -> LDT_ jc mlu30 ; CY = no more LDT's test es:LDT_FLAGS[bx],LFLG_NETWRKD+LFLG_JOINED jnz mlu20 ; if networked leave it alone cmp dl,es:LDT_DRV[bx] ; does the physical drive match ? jne mlu20 mov es:LDT_BLK[bx],0FFFFh ; indicate we shouldn't trust BLK mlu20: inc ax ; onto next LDT jmps mlu10 mlu30: pop bx pop ax pop es ret ;----------- write_block: ;----------- ; entry: RWMODE = write type ; bit 0: ; 1 - write, not read ; bits 2-1 (affected disk area) ; 0 0 - system area ; 0 1 - FAT area ; 1 0 - root or sub directory ; 1 1 - data area or rwmode,1 ; mark it as a write xor cx,cx ; indicate no second attempt mov al,CMD_OUTPUT ; assume normal write cmp verify_flag,0 ; is verify on ? je rdwr_block mov al,CMD_OUTPUT_VERIFY ; assume use write w/ verify jmps rdwr_block ;---------- read_block: ;---------- ; entry: RWMODE = read type ; bit 0: ; 0 - read, not write ; bits 2-1 (affected disk area) ; 0 0 - system area ; 0 1 - FAT area ; 1 0 - root or sub directory ; 1 1 - data area ; CX <> 0 if FAT retry possible (critical error should then ; be avoided) ; exit: SF = 0 if success ; SF = 1 if failure (CX was non-zero on call) and rwmode,not 1 ;mark it as a read mov al,CMD_INPUT rdwr_block: push cx call blockif ;current drive, track,.... pop cx jns rdwrb5 jcxz rdwrb10 ; test if any disk error detected rdwrb5: ret ; skip if yes rdwrb10: mov ioexerr,al ; save extended error test al,al ; is it write protect error ? jnz rdwrb20 ; we have dirty buffers we can't write call discard_dirty ; out, so throw 'em away rdwrb20: mov al,adrive ; if error on different drive cmp al,physical_drv ; treat error as media change je generate_critical_error ; if same drive, report error call discard_all ; discard all buffers on drive call discard_files ; and flush files jmp fdos_restart ; try to restart the instruction generate_critical_error: ;----------------------- ; On Entry: ; err_drv, rwmode, ioexerr set up ; On Exit: ; None - we don't come back ; mov al,ioexerr ; AL = BIOS error return byte cbw ; make it a word cmp ax,15 ; only handle sensible errors jb gen_crit_err10 ; anything else becomes mov ax,12 ; general failure gen_crit_err10: neg ax ; convert to our negative errors add ax,ED_PROTECT ; and start with write protect jmp fdos_error ; now return with error eject clus2sec: ; convert from cluster/offset to sector/offset ;-------- ; On Entry: ; AX = cluster ; BX = byte offset in cluster ; On Exit: ; DX:AX = sector ; BX = byte offset in sector ; xchg ax,cx ; remember cluster in CX xor dx,dx xchg ax,bx ; DX:AX = byte offset div psecsiz ; AX = sector offset, DX = byte offset mov bx,dx ; BX = byte offset in sector xchg ax,cx ; AX = cluster, CX = sector offset dec ax dec ax ; forget about 2 reserved clusters mul secperclu ; DX:AX = offset of cluster add ax,datadd adc dx,0 ; DX:AX = offset of start of dir add ax,cx ; DX:AX - add in sector offset adc dx,0 ret end