INT 21h AH=3Dh finds an existing file and opens it. Nothing about the file changes: the contents and the length stay, and the file pointer starts at offset 0, so the first read gets the first byte and the first write overwrites it. To append, open and then move the pointer to the end with INT 21h AH=42h. If the file is not there the call fails; making files is the job of INT 21h AH=3Ch.
| AL | Meaning |
|---|---|
| 00h | read only: a write through this handle fails with 05h |
| 01h | write only: a read fails with 05h |
| 02h | read and write |
| bits 4-6 | sharing (DOS 3+): 00h compatibility, 10h deny others all access, 20h deny write, 30h deny read, 40h deny nothing |
| bit 7 (80h) | private: a program started with EXEC does not inherit the handle |
The sharing bits matter only with SHARE.EXE loaded or on a network, where they decide whether a second open of the same file succeeds; ordinary programs use 00h, 01h or 02h. A value of 3 or more in bits 0-2 is error 0Ch, invalid access code.
The name can also be a device: opening CON, NUL or PRN gives a handle to the device, and reads and writes go to it. INT 21h AH=44h tells a device from a file.
Errors
| AX | Meaning |
|---|---|
| 02h | file not found: the directory is there, the file is not |
| 03h | path not found: a directory in the name does not exist |
| 04h | too many open files: all 20 handles of the program are in use, or the system table (FILES=) is full |
| 05h | access denied: AL asks to write a read-only file, or the name is a directory |
| 0Ch | invalid access code in AL |
MOV DX, OFFSET M1MOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AX, 3D00h ; DOS: open, AL = 0: for readingMOV DX, OFFSET NAME1 ; a file that is not thereINT 21hCALL REPORTMOV DX, OFFSET M2MOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AX, 3D00hMOV DX, OFFSET NAME2 ; in a directory that is not thereINT 21hCALL REPORTNAME1 DB "NO_SUCH.TXT", 0NAME2 DB "NO_SUCH\TEST.TXT", 0M1 DB "NO_SUCH.TXT: $"M2 DB "NO_SUCH\TEST.TXT: $"CRLF DB 13, 10, "$"REPORT PROC NEAR ; CF = 0: "ok"; CF = 1: "error" and ALJC REPORT1MOV DX, OFFSET OKMSGMOV AH, 09hINT 21hRETREPORT1:PUSH AXMOV DX, OFFSET ERRMSGMOV AH, 09hINT 21hPOP AXCALL PHEXB ; the error codeMOV DX, OFFSET CRLFMOV AH, 09hINT 21hRETREPORT ENDPOKMSG DB "ok", 13, 10, "$"ERRMSG DB "error $"PHEXB PROC NEAR ; print AL as two hex digitsPUSH CXPUSH DXMOV DH, AL ; keep the byteMOV CL, 4SHR AL, CL ; the high digit firstMOV CX, 2PHEXB1:AND AL, 0FhADD AL, "0"CMP AL, "9"JBE PHEXB2ADD AL, 7 ; 10..15 -> "A".."F"PHEXB2:MOV DL, ALMOV AH, 02h ; DOS: print DLINT 21hMOV AL, DH ; then the low digitLOOP PHEXB1POP DXPOP CXRETPHEXB ENDP
The access mode is checked on every read and write, not only at the open. Here a file opened with AL = 0 refuses a write and allows a read:
MOV AH, 3Ch ; DOS: create TEST.TXTXOR CX, CX ; CX = 0: normal attributesMOV DX, OFFSET FNAMEINT 21hCALL CHECK ; stop if CF = 1MOV BX, AX ; BX = the handleMOV AH, 40h ; put "DOS" in itMOV CX, 3MOV DX, OFFSET TXT1INT 21hCALL CHECKMOV AH, 3Eh ; closeINT 21hMOV AX, 3D00h ; DOS: open it again, read onlyMOV DX, OFFSET FNAMEINT 21hCALL CHECKMOV BX, AXMOV DX, OFFSET M1MOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AH, 40h ; try to write 3 bytesMOV CX, 3MOV DX, OFFSET TXT1INT 21hCALL REPORT ; CF = 1, AX = 05hMOV AH, 3Fh ; reading is allowedMOV CX, 3MOV DX, OFFSET BUFINT 21hCALL CHECKMOV SI, AX ; SI = bytes readMOV DX, OFFSET M2MOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV CX, SIMOV DX, OFFSET BUFCALL OUT1 ; print themMOV DX, OFFSET CRLFMOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AH, 3Eh ; close the handleINT 21hMOV AH, 41h ; delete TEST.TXTMOV DX, OFFSET FNAMEINT 21hFNAME DB "TEST.TXT", 0TXT1 DB "DOS"M1 DB "write through a read-only handle: $"M2 DB "read back: $"CRLF DB 13, 10, "$"BUF DB 64 DUP (?)CHECK PROC NEAR ; CF = 1: print the error code in AL and exitJNC CHECK1PUSH AXMOV DX, OFFSET FAILMSGMOV AH, 09hINT 21hPOP AXCALL PHEXBMOV AX, 4C01h ; exit with code 1INT 21hCHECK1:RETCHECK ENDPFAILMSG DB "failed: error $"REPORT PROC NEAR ; CF = 0: "ok"; CF = 1: "error" and ALJC REPORT1MOV DX, OFFSET OKMSGMOV AH, 09hINT 21hRETREPORT1:PUSH AXMOV DX, OFFSET ERRMSGMOV AH, 09hINT 21hPOP AXCALL PHEXB ; the error codeMOV DX, OFFSET CRLFMOV AH, 09hINT 21hRETREPORT ENDPOKMSG DB "ok", 13, 10, "$"ERRMSG DB "error $"PHEXB PROC NEAR ; print AL as two hex digitsPUSH CXPUSH DXMOV DH, AL ; keep the byteMOV CL, 4SHR AL, CL ; the high digit firstMOV CX, 2PHEXB1:AND AL, 0FhADD AL, "0"CMP AL, "9"JBE PHEXB2ADD AL, 7 ; 10..15 -> "A".."F"PHEXB2:MOV DL, ALMOV AH, 02h ; DOS: print DLINT 21hMOV AL, DH ; then the low digitLOOP PHEXB1POP DXPOP CXRETPHEXB ENDPOUT1 PROC NEAR ; write CX bytes at DS:DX to handle 1PUSH BXMOV BX, 1 ; standard outputMOV AH, 40hINT 21hPOP BXRETOUT1 ENDP
Pitfalls
Test CF before using AX. On failure AX holds the error code, a small number that looks like a perfectly good handle (2 is standard error), and writing to it puts your data on the screen. Handles are also a limited resource: a program that opens files in a loop and forgets to close them gets 04h after about fifteen.
DOSBox-X accepts the sharing bits but passes them on to the host only on Windows. On a host folder, whether a read-only file can really be written also depends on the host's file permissions.