2 ; Disk routines for MSDOS
7 CODE SEGMENT BYTE PUBLIC 'CODE'
8 ASSUME
SS:DOSGROUP
,CS:DOSGROUP
17 TITLE DISK
- Disk utility routines
42 i_need SecClusPos
,BYTE
45 i_need NxtClusNum
,WORD
62 SUBTTL
LOAD -- MAIN READ ROUTINE
AND DEVICE
IN ROUTINES
64 ; * * * * Drivers for file input from devices * * * *
66 procedure SWAPBACK
,NEAR
67 ASSUME
DS:DOSGROUP
,ES:NOTHING
73 invoke get_sf_from_jfn
75 MOV BL,BYTE PTR [COUTDSAV
]
78 MOV WORD PTR ES:[DI.fcb_FIRCLUS
],SI
79 MOV WORD PTR ES:[DI.fcb_FIRCLUS
+2],DS
80 MOV ES:[DI.fcb_DEVID
],BL
85 invoke get_sf_from_jfn
87 MOV BL,BYTE PTR [CINDSAV
]
90 MOV WORD PTR ES:[DI.fcb_FIRCLUS
],SI
91 MOV WORD PTR ES:[DI.fcb_FIRCLUS
+2],DS
92 MOV ES:[DI.fcb_DEVID
],BL
96 MOV BYTE PTR [CONSWAP
],0
97 MOV BYTE PTR [IDLEINT
],1
106 procedure SWAPCON
,NEAR
107 ASSUME
DS:DOSGROUP
,ES:NOTHING
112 MOV BYTE PTR [CONSWAP
],1
113 MOV BYTE PTR [IDLEINT
],0
115 invoke get_sf_from_jfn
117 MOV BL,ES:[DI.fcb_DEVID
]
118 MOV BYTE PTR [CINDSAV
],BL
119 LDS SI,DWORD PTR ES:[DI.fcb_FIRCLUS
]
121 MOV WORD PTR [CINSAV
],SI
122 MOV WORD PTR [CINSAV
+2],DS
124 MOV BL,[SI.fcb_DEVID
]
125 LDS SI,DWORD PTR [SI.fcb_FIRCLUS
]
126 MOV ES:[DI.fcb_DEVID
],BL
127 MOV WORD PTR ES:[DI.fcb_FIRCLUS
],SI
128 MOV WORD PTR ES:[DI.fcb_FIRCLUS
+2],DS
133 invoke get_sf_from_jfn
135 MOV BL,ES:[DI.fcb_DEVID
]
136 MOV BYTE PTR [COUTDSAV
],BL
137 LDS SI,DWORD PTR ES:[DI.fcb_FIRCLUS
]
139 MOV WORD PTR [COUTSAV
],SI
140 MOV WORD PTR [COUTSAV
+2],DS
142 MOV BL,[SI.fcb_DEVID
]
143 LDS SI,DWORD PTR [SI.fcb_FIRCLUS
]
144 MOV ES:[DI.fcb_DEVID
],BL
145 MOV WORD PTR ES:[DI.fcb_FIRCLUS
],SI
146 MOV WORD PTR ES:[DI.fcb_FIRCLUS
+2],DS
153 ASSUME
DS:NOTHING
,ES:NOTHING
157 ; DX:AX = Position in file to read
158 ; CX = No. of records to read
160 ; DX:AX = Position of last record read
161 ; CX = No. of bytes read
163 ; fcb_LSTCLUS, fcb_CLUSPOS fields in FCB set
167 OR BL,BL ; Check for named device I/O
173 ASSUME
DS:DOSGROUP
,ES:NOTHING
175 TEST BL,40H
; End of file?
177 TEST BL,ISNULL
; NUL device?
179 XOR AL,AL ; Indicate EOF
180 ENDRDDEVJ3: JMP ENDRDDEVJ2
188 MOV BX,DI ; DS:BX transfer addr
189 XOR DX,DX ; Start at 0
190 XOR AX,AX ; Media Byte, unit = 0
194 MOV DX,DI ; DX is preserved by INT 24
195 MOV AH,86H
; Read error
196 MOV DI,[DEVCALL
.REQSTAT
]
198 JZ CRDROK
; No errors
205 ADD DI,[CALLSCNT
] ; Amount transferred
209 TEST BL,020H ; Raw mode?
211 TEST BL,ISCIN
; Is it console device?
226 LDS SI,DWORD PTR [SI.fcb_FIRCLUS
]
232 MOV DI,[DEVCALL
.REQSTAT
]
240 XOR AL,AL ;Pick some random character
247 MOV DS,WORD PTR [CALLXAD
+2]
251 INC WORD PTR [CALLXAD
]
252 MOV [DEVCALL
.REQSTAT
],0
263 ASSUME
DS:NOTHING
,ES:NOTHING
268 CMP AL,c_CR
; Check for carriage return
270 MOV BYTE PTR [SI],c_LF
275 XOR SI,SI ; Cause a new buffer to be read
276 invoke
OUT ; Transmit linefeed
277 OR AL,1 ; Clear zero flag--not end of file
289 JNZ SETFCBC
; Zero set if Ctrl-Z found in input
291 AND ES:BYTE PTR [DI.fcb_DEVID
],0FFH-40H
; Mark as no more data available
296 ASSUME
DS:NOTHING
,ES:NOTHING
304 CMP BYTE PTR [CONBUF
],128
306 MOV WORD PTR [CONBUF
],0FF80H ; Set up 128-byte buffer with no template
311 MOV DX,OFFSET DOSGROUP
:CONBUF
312 invoke $STD_CON_STRING_INPUT
; Get input buffer
316 MOV SI,2 + OFFSET DOSGROUP
:CONBUF
317 CMP BYTE PTR [SI],1
AH ; Check for Ctrl-Z in first character
323 invoke
OUT ; Send linefeed
329 SUBTTL
STORE -- MAIN WRITE ROUTINE
AND DEVICE
OUT ROUTINES
331 ASSUME
DS:NOTHING
,ES:NOTHING
333 ASSUME
DS:NOTHING
,ES:NOTHING
337 ; DX:AX = Position in file of disk transfer
340 ; DX:AX = Position of last record written
341 ; CX = No. of records written
343 ; fcb_LSTCLUS, fcb_CLUSPOS fields in FCB set
350 MOV ES:[DI.fcb_FDATE
],AX
351 MOV ES:[DI.fcb_FTIME
],DX
373 SUB AX,CX ; Amount actually written
381 XOR AX,AX ; Media Byte, unit = 0
387 MOV DI,[DEVCALL
.REQSTAT
]
391 MOV BX,DX ; Recall transfer addr
393 JZ DVWRTRAW
; Try again
397 MOV AX,[CALLSCNT
] ; Get actual number of bytes transferred
401 DIV ES:[DI.fcb_RECSIZ
]
402 MOV CX,AX ; Partial record is ignored
408 OR BL,40H
; Reset EOF for input
410 JCXZ ENDWRDEV
; problem of creating on a device.
416 XOR DX,DX ; Set starting point
419 TEST AL,ISCOUT
; Console output device?
424 CMP BYTE PTR [BX],1
AH ; ^Z?
425 JZ WRTCOOKDONE
; Yes, transfer nothing
431 LDS SI,DWORD PTR [SI.fcb_FIRCLUS
]
437 MOV DI,[DEVCALL
.REQSTAT
]
452 INC WORD PTR [CALLXAD
]
455 MOV DS,WORD PTR [CALLXAD
+2]
456 CMP BYTE PTR [DI],1
AH ; ^Z?
459 MOV [DEVCALL
.REQSTAT
],0
467 MOV DX,CX ;Entire transfer done
472 procedure get_io_fcb
,near
473 ASSUME
DS:NOTHING
,ES:NOTHING
474 ; Convert JFN number in BX to FCB in DS:SI
480 invoke get_sf_from_jfn
493 SUBTTL GETTHISDRV
-- FIND CURRENT DRIVE
495 ; Input: AL has drive identifier (1=A, 0=default)
496 ; Output: AL has physical drive (0=A)
497 ; Carry set if invalid drive (and AL is garbage anyway)
498 procedure GetThisDrv
,NEAR
499 ASSUME
DS:NOTHING
,ES:NOTHING
500 CMP BYTE PTR [NUMIO
],AL
506 MOV BYTE PTR [THISDRV
],AL
510 SUBTTL DIRREAD
-- READ A DIRECTORY SECTOR
512 procedure DirRead
,NEAR
513 ASSUME
DS:DOSGROUP
,ES:NOTHING
516 ; AX = Directory block number (relative to first block of directory)
517 ; ES:BP = Base of drive parameters
518 ; [DIRSEC] = First sector of first cluster of directory
519 ; [CLUSNUM] = Next cluster
520 ; [CLUSFAC] = Sectors/Cluster
522 ; Read the directory block into [CURBUF].
524 ; [NXTCLUSNUM] = Next cluster (after the one skipped to)
526 ; ES:BP unchanged [CURBUF] Points to Buffer with dir sector
527 ; All other registers destroyed.
530 DIV CL ; AL # clusters to skip, AH position in cluster
552 XOR AL,AL ; Indicate pre-read
558 SUBTTL FATSECRD
-- READ A FAT SECTOR
560 procedure FATSecRd
,NEAR
561 ASSUME
DS:NOTHING
,ES:NOTHING
565 ; DS:BX = Transfer address
566 ; CX = Number of sectors
567 ; DX = Absolute record number
568 ; ES:BP = Base of drive parameters
570 ; Calls BIOS to perform FAT read.
575 MOV CL,ES:[BP.dpb_FAT_count
]
576 MOV AL,ES:[BP.dpb_FAT_size
]
595 SUBTTL DREAD
-- DO A DISK READ
598 ASSUME
DS:NOTHING
,ES:NOTHING
601 ; DS:BX = Transfer address
602 ; CX = Number of sectors
603 ; DX = Absolute record number
604 ; ES:BP = Base of drive parameters
606 ; Calls BIOS to perform disk read. If BIOS reports
607 ; errors, will call HARDERR for further action.
608 ; DS,ES:BP preserved. All other registers destroyed.
612 MOV BYTE PTR [READOP
],0
614 CMP AL,1 ; Check for retry
616 return
; Ignore otherwise
621 SUBTTL DSKREAD
-- PHYSICAL DISK READ
623 procedure DskRead
,NEAR
624 ASSUME
DS:NOTHING
,ES:NOTHING
627 ; DS:BX = Transfer addr
628 ; CX = Number of sectors
629 ; DX = Absolute record number
630 ; ES:BP = Base of drive parameters
632 ; Call BIOS to perform disk read
635 ; CX = Number of sectors unsuccessfully transfered
636 ; AX = Status word as returned by BIOS (error code in AL if error)
637 ; Zero set if OK (from BIOS)
638 ; Zero clear if error
639 ; SI Destroyed, others preserved
642 MOV AH,ES:[BP.dpb_media
]
643 MOV AL,ES:[BP.dpb_UNIT
]
649 SUBTTL DWRITE
-- SEE ABOUT WRITING
652 ASSUME
DS:NOTHING
,ES:NOTHING
655 ; DS:BX = Transfer address
656 ; CX = Number of sectors
657 ; DX = Absolute record number
658 ; ES:BP = Base of drive parameters
660 ; Calls BIOS to perform disk write. If BIOS reports
661 ; errors, will call HARDERR for further action.
662 ; BP preserved. All other registers destroyed.
666 MOV BYTE PTR [READOP
],1
668 CMP AL,1 ; Check for retry
672 SUBTTL DSKWRITE
-- PHYSICAL DISK WRITE
675 ASSUME
DS:NOTHING
,ES:NOTHING
678 ; DS:BX = Transfer addr
679 ; CX = Number of sectors
680 ; DX = Absolute record number
681 ; ES:BP = Base of drive parameters
683 ; Call BIOS to perform disk read
686 ; CX = Number of sectors unsuccessfully transfered
687 ; AX = Status word as returned by BIOS (error code in AL if error)
688 ; Zero set if OK (from BIOS)
689 ; Zero clear if error
690 ; SI Destroyed, others preserved
693 MOV AH,ES:[BP.dpb_media
]
694 MOV AL,ES:[BP.dpb_UNIT
]
700 POP DS ; DS:BP points to DPB
702 LDS SI,DS:[BP.dpb_driver_addr
]
704 MOV DS,CX ; Restore DS
707 MOV CX,[CALLSCNT
] ; Number of sectors transferred
710 NEG CX ; Number of sectors not transferred
711 MOV AX,[DEVCALL
.REQSTAT
]
716 SUBTTL SETUP
-- SETUP A DISK READ
OR WRITE
FROM USER
718 ASSUME
DS:DOSGROUP
,ES:NOTHING
721 ASSUME
DS:NOTHING
,ES:NOTHING
725 ; DX:AX = Record position in file of disk transfer
729 ; BL = fcb_DEVID from FCB
730 ; CX = No. of bytes to transfer (0 = 64K)
731 ; [THISDPB] = Base of drive parameters
732 ; [RECCNT] = Record count
733 ; [RECPOS] = Record position in file
734 ; ES:DI Points to FCB
736 ; [NEXTADD] = Displacement of disk transfer within segment
737 ; [SECPOS] = Position of first sector
738 ; [BYTPOS] = Byte position in file
739 ; [BYTSECPOS] = Byte position in first sector
740 ; [CLUSNUM] = First cluster
741 ; [SECCLUSPOS] = Sector within first cluster
742 ; [DSKERR] = 0 (no errors yet)
743 ; [TRANS] = 0 (No transfers yet)
744 ; [THISDRV] = Physical drive unit number
749 MOV BYTE PTR [THISDRV
],AL
750 MOV AL,[DI.fcb_DEVID
]
751 MOV SI,[DI.fcb_RECSIZ
]
755 MOV [DI.fcb_RECSIZ
],SI
757 MOV WORD PTR [THISFCB
+2],DS
759 POP DS ; Set DS to DOSGROUP
761 MOV WORD PTR [THISFCB
],DI
762 OR AL,AL ; Is it a device?
764 XOR AL,AL ; Fake in drive 0 so we can get BP
770 MOV BYTE PTR [DSKERR
],4
775 CMP SI,64 ; Check if highest byte of RECPOS is significant
777 XOR DH,DH ; Ignore MSB if record >= 64 bytes
780 MOV WORD PTR [RECPOS
],AX
781 MOV WORD PTR [RECPOS
+2],DX
782 MOV BX,WORD PTR [DMAADD
]
784 MOV BYTE PTR [DSKERR
],0
785 MOV BYTE PTR [TRANS
],0
788 MOV WORD PTR [BYTPOS
],AX
794 ADC DX,0 ; Ripple carry
796 MOV WORD PTR [BYTPOS
+2],AX
798 MOV AX,WORD PTR [BYTPOS
]
799 MOV BX,ES:[BP.dpb_sector_size
]
800 CMP DX,BX ; See if divide will overflow
806 AND AL,ES:[BP.dpb_cluster_mask
]
808 MOV AX,CX ; Record count
809 MOV CL,ES:[BP.dpb_cluster_shift
]
812 MUL SI ; Multiply by bytes per record
814 ADD AX,WORD PTR [DMAADD
] ; See if it will fit in one segment
816 JZ OK
; Must be less than 64K
817 MOV AX,WORD PTR [DMAADD
]
818 NEG AX ; Amount of room left in segment
823 DIV SI ; How many records will fit?
825 MUL SI ; Translate that back into bytes
826 MOV BYTE PTR [DSKERR
],2 ; Flag that trimming took place
831 MOV BL,ES:[DI.fcb_DEVID
]
835 MOV BYTE PTR [DSKERR
],1
839 POP BX ; Kill return address
843 SUBTTL BREAKDOWN
-- CUT A USER READ
OR WRITE
INTO PIECES
845 procedure BREAKDOWN
,near
846 ASSUME
DS:DOSGROUP
,ES:NOTHING
849 ; CX = Length of disk transfer in bytes
850 ; ES:BP = Base of drive parameters
851 ; [BYTSECPOS] = Byte position witin first sector
853 ; [BYTCNT1] = Bytes to transfer in first sector
854 ; [SECCNT] = No. of whole sectors to transfer
855 ; [BYTCNT2] = Bytes to transfer in last sector
856 ; AX, BX, DX destroyed. No other registers affected.
861 JZ SAVFIR
; Partial first sector?
862 SUB AX,ES:[BP.dpb_sector_size
]
863 NEG AX ; Max number of bytes left in first sector
864 SUB BX,AX ; Subtract from total length
866 ADD AX,BX ; Don't use all of the rest of the sector
867 XOR BX,BX ; And no bytes are left
872 DIV ES:[BP.dpb_sector_size
] ; How many whole sectors?
874 MOV [BYTCNT2
],DX ; Bytes remaining for last sector
876 retnz
; NOT (BYTCNT1 = BYTCNT2 = 0)
879 MOV AX,ES:[BP.dpb_sector_size
] ; Buffer EXACT one sector I/O
881 MOV [SECCNT
],DX ; DX = 0
885 SUBTTL DISKREAD
-- PERFORM USER DISK READ
887 procedure DISKREAD
,NEAR
888 ASSUME
DS:DOSGROUP
,ES:NOTHING
895 ; DX:AX = Position of last record read
896 ; CX = No. of records read
898 ; fcb_LSTCLUS, fcb_CLUSPOS fields in FCB set
900 MOV AX,ES:WORD PTR [DI.fcb_FILSIZ
]
901 MOV BX,ES:WORD PTR [DI.fcb_FILSIZ
+2]
902 SUB AX,WORD PTR [BYTPOS
]
903 SBB BX,WORD PTR [BYTPOS
+2]
935 MOV BYTE PTR [TRANS
],1 ; A transfer is taking place
945 MOV DS,WORD PTR [DMAADD
+2]
952 ADD BX,DX ; Upper bound of read
953 MOV AL,ES:[BP.dpb_drive
]
955 NXTBUF: ; Must see if one of these sectors is buffered
956 MOV [DI.VISIT
],1 ; Mark as visited
958 JNZ DONXTBUF
; Not for this drive
960 JC DONXTBUF
; Below first sector
962 JNC DONXTBUF
; Above last sector
963 CMP BYTE PTR [DI.BUFDIRTY
],0
964 JZ CLBUFF
; Buffer is clean, so OK
965 ; A sector has been read in when a dirty copy of it is in a buffer
966 ; The buffered sector must now be read into the right place
967 POP AX ; Recall transfer address
969 PUSH DI ; Save search environment
971 SUB DX,[DI.BUFSECNO
] ; How far into transfer?
976 MOV CX,ES:[BP.dpb_sector_size
]
978 ADD DI,AX ; Put the buffer here
982 MOV ES,WORD PTR [DMAADD
+2]
990 MOV AL,ES:[BP.dpb_drive
]
1006 INC [LASTPOS
] ; We'll be using next cluster
1023 SUB AX,WORD PTR [DMAADD
] ; Number of bytes transfered
1025 MOV CX,ES:[SI.fcb_RECSIZ
]
1026 DIV CX ; Number of records
1027 CMP AX,[RECCNT
] ; Check if all records transferred
1029 MOV BYTE PTR [DSKERR
],1
1031 JZ FULLREC
; If remainder 0, then full record transfered
1032 MOV BYTE PTR [DSKERR
],3 ; Flag partial last record
1033 SUB CX,DX ; Bytes left in last record
1035 MOV ES,WORD PTR [DMAADD
+2]
1036 XCHG AX,BX ; Save the record count temporarily
1037 XOR AX,AX ; Fill with zeros
1043 XCHG AX,BX ; Restore record count to AX
1045 INC AX ; Add last (partial) record to total
1048 MOV DI,SI ; ES:DI point to FCB
1050 TEST ES:[DI].fcb_DEVID
,-1
1051 JS ADDREC
; don't set clisters if device
1053 AND ES:[DI.fcb_LSTCLUS
],0F000h ; fcb_lstclus is packed with dir clus
1054 OR ES:[DI.fcb_LSTCLUS
],AX ; drop in the correct part of fcb_lstclus
1056 MOV ES:[DI.fcb_CLUSPOS
],AX
1058 MOV AX,WORD PTR [RECPOS
]
1059 MOV DX,WORD PTR [RECPOS
+2]
1060 JCXZ RET28
; If no records read, don't change position
1062 ADD AX,CX ; Update current record position
1068 SUBTTL DISKWRITE
-- PERFORM USER DISK WRITE
1070 procedure DISKWRITE
,NEAR
1071 ASSUME
DS:DOSGROUP
,ES:NOTHING
1076 ; Perform disk write
1078 ; DX:AX = Position of last record written
1079 ; CX = No. of records written
1080 ; ES:DI point to FCB
1081 ; fcb_LSTCLUS, fcb_CLUSPOS fields in FCB set
1083 AND BL,3FH
; Mark file as dirty
1084 MOV ES:[DI.fcb_DEVID
],BL
1087 MOV AX,WORD PTR [BYTPOS
]
1088 MOV DX,WORD PTR [BYTPOS
+2]
1091 ADC DX,0 ; AX:DX=last byte accessed
1092 DIV ES:[BP.dpb_sector_size
] ; AX=last sector accessed
1093 MOV BX,AX ; Save last full sector
1096 DEC AX ; AX must be zero base indexed
1098 MOV CL,ES:[BP.dpb_cluster_shift
]
1099 SHR AX,CL ; Last cluster to be accessed
1101 PUSH DX ; Save the size of the "tail"
1104 MOV AX,ES:WORD PTR [DI.fcb_FILSIZ
]
1105 MOV DX,ES:WORD PTR [DI.fcb_FILSIZ
+2]
1107 DIV ES:[BP.dpb_sector_size
]
1108 MOV CX,AX ; Save last full sector of current file
1111 INC AX ; Round up if any remainder
1113 MOV [VALSEC
],AX ; Number of sectors that have been written
1115 MOV WORD PTR [GROWCNT
],AX
1116 MOV WORD PTR [GROWCNT
+2],AX
1118 SUB BX,CX ; Number of full sectors
1123 MUL ES:[BP.dpb_sector_size
] ; Bytes of full sector growth
1124 SUB AX,CX ; Take off current "tail"
1125 SBB DX,0 ; 32-bit extension
1126 ADD AX,BX ; Add on new "tail"
1127 ADC DX,0 ; ripple tim's head off
1138 MOV BYTE PTR [DSKERR
],1
1139 MOV AX,WORD PTR [RECPOS
]
1140 MOV DX,WORD PTR [RECPOS
+2]
1154 MOV WORD PTR [GROWCNT
],AX
1155 MOV WORD PTR [GROWCNT
+2],DX
1158 MOV CX,[CLUSNUM
] ; First cluster accessed
1162 SUB AX,DX ; Last cluster minus current cluster
1163 JZ DOWRT
; If we have last clus, we must have first
1164 JCXZ HAVSTART
; See if no more data
1165 PUSH CX ; No. of clusters short of first
1190 MOV BYTE PTR [TRANS
],1 ; A transfer is taking place
1200 MOV AL,ES:[BP.dpb_drive
]
1202 ADD BX,DX ; Upper bound of write
1205 NEXTBUFF: ; Search for buffers
1206 MOV [DI.VISIT
],1 ; Mark as visited
1208 JNZ DONEXTBUFF
; Not for this drive
1209 CMP [DI.BUFSECNO
],DX
1210 JC DONEXTBUFF
; Buffer is not in range of write
1211 CMP [DI.BUFSECNO
],BX
1212 JNC DONEXTBUFF
; Buffer is not in range of write
1213 MOV WORD PTR [DI.BUFDRV
],00FFH ; Free the buffer, it is being over written
1220 MOV DS,WORD PTR [DMAADD
+2]
1229 INC [LASTPOS
] ; We'll be using next cluster
1244 MOV AX,WORD PTR [GROWCNT
]
1245 MOV CX,WORD PTR [GROWCNT
+2]
1251 ADD WORD PTR ES:[DI.fcb_FILSIZ
],AX
1252 ADC WORD PTR ES:[DI.fcb_FILSIZ
+2],CX
1263 DIV ES:[BP.dpb_sector_size
]
1264 MOV CL,ES:[BP.dpb_cluster_shift
]
1273 MOV AX,WORD PTR [BYTPOS
]
1274 MOV ES:WORD PTR [DI.fcb_FILSIZ
],AX
1275 MOV AX,WORD PTR [BYTPOS
+2]
1276 MOV ES:WORD PTR [DI.fcb_FILSIZ
+2],AX
1289 MOV ES:[DI.fcb_CLUSPOS
],BX
1290 XCHG BX,ES:[DI.fcb_FIRCLUS
]
1291 AND ES:[DI.fcb_LSTCLUS
],0F000H