Every open handle has a file pointer: the offset where the next INT 21h AH=3Fh reads or INT 21h AH=40h writes. Both move it forward. INT 21h AH=42h sets it directly. The offset is a signed 32-bit number split across two registers, CX the high word and DX the low word, and it counts from the origin chosen in AL.

ALNew positionTypical use
0the offset from the startgo to record n: offset = n × record size
1the current position + the offsetskip forward or back; offset 0 asks where you are
2the end of the file + the offsetoffset 0: append there, and DX:AX = the file size

The result in DX:AX is always the new position counted from the start of the file. Nothing is read or written and the disk is not touched: the call only changes a number in DOS's table of open files.

prints four lines: "size: 10", "at 3: 34", "now at: 5" and "end - 2: 89"
MOV AH, 3Ch ; DOS: create TEST.TXT
XOR CX, CX ; CX = 0: normal attributes
MOV DX, OFFSET FNAME
INT 21h
CALL CHECK ; stop if CF = 1
MOV BX, AX ; BX = the handle
MOV AH, 40h ; write ten digits
MOV CX, 10
MOV DX, OFFSET DIGITS
INT 21h
CALL CHECK
MOV AX, 4202h ; DOS: seek, AL = 2: from the end
XOR CX, CX ; CX:DX = 0
XOR DX, DX
INT 21h
CALL CHECK
MOV SI, AX ; DX:AX = the size (DX = 0 here)
MOV DX, OFFSET M1
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, SI
CALL PDEC
MOV DX, OFFSET CRLF
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, 4200h ; AL = 0: from the start, CX:DX = 3
XOR CX, CX
MOV DX, 3
INT 21h
CALL CHECK
MOV DX, OFFSET M2
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
CALL READ2 ; read 2 bytes and print them
MOV AX, 4201h ; AL = 1: from here, offset 0
XOR CX, CX
XOR DX, DX
INT 21h
CALL CHECK
MOV SI, AX ; where the pointer is now
MOV DX, OFFSET M3
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, SI
CALL PDEC
MOV DX, OFFSET CRLF
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, 4202h ; AL = 2, CX:DX = -2
MOV CX, 0FFFFh
MOV DX, 0FFFEh
INT 21h
CALL CHECK
MOV DX, OFFSET M4
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
CALL READ2
MOV AH, 3Eh ; close the handle
INT 21h
MOV AH, 41h ; delete TEST.TXT
MOV DX, OFFSET FNAME
INT 21h
FNAME DB "TEST.TXT", 0
DIGITS DB "0123456789"
M1 DB "size: $"
M2 DB "at 3: $"
M3 DB "now at: $"
M4 DB "end - 2: $"
READ2 PROC NEAR ; read 2 bytes from BX, print them and CR LF
MOV AH, 3Fh
MOV CX, 2
MOV DX, OFFSET BUF
INT 21h
CALL CHECK
MOV CX, AX ; DX still = OFFSET BUF
CALL OUT1
MOV DX, OFFSET CRLF
MOV AH, 09h
INT 21h
RET
READ2 ENDP
CRLF DB 13, 10, "$"
BUF DB 64 DUP (?)
CHECK PROC NEAR ; CF = 1: print the error code in AL and exit
JNC CHECK1
PUSH AX
MOV DX, OFFSET FAILMSG
MOV AH, 09h
INT 21h
POP AX
CALL PHEXB
MOV AX, 4C01h ; exit with code 1
INT 21h
CHECK1:
RET
CHECK ENDP
FAILMSG DB "failed: error $"
PHEXB PROC NEAR ; print AL as two hex digits
PUSH CX
PUSH DX
MOV DH, AL ; keep the byte
MOV CL, 4
SHR AL, CL ; the high digit first
MOV CX, 2
PHEXB1:
AND AL, 0Fh
ADD AL, "0"
CMP AL, "9"
JBE PHEXB2
ADD AL, 7 ; 10..15 -> "A".."F"
PHEXB2:
MOV DL, AL
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AL, DH ; then the low digit
LOOP PHEXB1
POP DX
POP CX
RET
PHEXB ENDP
PDEC PROC NEAR ; print AX in decimal
PUSH BX
PUSH CX
PUSH DX
MOV BX, 10
XOR CX, CX ; CX = digits pushed
PDEC1:
XOR DX, DX
DIV BX ; AX = AX / 10, DX = the last digit
PUSH DX
INC CX
OR AX, AX
JNZ PDEC1
PDEC2:
POP DX ; the digits come back first to last
ADD DL, "0"
MOV AH, 02h
INT 21h
LOOP PDEC2
POP DX
POP CX
POP BX
RET
PDEC ENDP
OUT1 PROC NEAR ; write CX bytes at DS:DX to handle 1
PUSH BX
MOV BX, 1 ; standard output
MOV AH, 40h
INT 21h
POP BX
RET
OUT1 ENDP

A negative offset is the 32-bit two's complement spread over CX:DX: -2 is CX = FFFFh, DX = FFFEh. For a backward move by a 16-bit amount, put its negation in DX and FFFFh in CX.

Pitfalls

The size comes back in DX:AX, and a file over 64 KB has DX ≠ 0: do not keep only AX. Asking for the size moves the pointer to the end, so seek back (AL = 0, CX = DX = 0) before reading the file. A position past the end is allowed: a read there returns 0 bytes and a write there extends the file.

A position before the start is accepted by MS-DOS without complaint and leaves the trouble to the next read or write. DOSBox-X, on a host folder, moves the pointer to the end of the file instead. Do not rely on either. On a device the call succeeds and changes nothing: devices have no position.