Skip to content
FreeDOSDeep Dive Published Updated 7 min readViews unavailable

DOS File Positioning with INT 21h AH=42h: Origins, Offsets, and EOF

Use DOS INT 21h AH=42h safely by handling signed CX:DX offsets, carry-flag errors, past-EOF writes, and version-dependent large-file boundaries.

DOS INT 21h function AH=42h moves the current file position for an already-open handle. It does not read data, write data, resize a file immediately, or grant access to bytes that the filesystem and driver cannot represent. Programs use it to seek to the beginning, relative to the current offset, or relative to the end; later reads and writes use that position.

The call has a compact interface but several important boundaries. CX:DX is a signed 32-bit offset from the selected origin, the carry flag reports failure, and DX:AX returns the resulting absolute position. A seek beyond end-of-file can succeed without changing the file yet; a later write can extend it. Large-file support depends on the DOS version, filesystem, open mode, and driver, so a 32-bit register pair is not a promise that every 32-bit-sized file is supported.

Register contract

The documented DOS call uses AH=42h, AL for the origin, BX for the open file handle, and CX:DX for the signed offset. The origin values are 00h from the start of file, 01h from the current file position, and 02h from end of file. A successful call clears carry and returns the new byte position in DX:AX. On failure, carry is set and AX contains an error code.

To query the file length for a normal file, seek zero bytes from the end. The returned position is then the file length and the handle remains positioned at that location. If the program needs to continue reading from its prior location, save the original position first and restore it after the size query.

The parameter is not two unrelated 16-bit numbers. CX is the high word and DX the low word of the signed displacement. For example, a positive offset of 0001:0000 advances 65,536 bytes from the selected origin. Negative values are represented in two’s-complement form and move backward. Incorrect word order, an unintended origin, or a nonzero stale register can seek to the wrong location while still returning success.

A minimal seek to the beginning

The following 16-bit assembly example assumes BX already contains a valid DOS file handle:

; Position the open handle at byte zero.
mov ax, 4200h       ; AH=42h, AL=00h: relative to file start
xor cx, cx          ; high word of signed offset
xor dx, dx          ; low word of signed offset
int 21h
jc seek_failed     ; do not trust DX:AX when carry is set
; On success, DX:AX is the new absolute position.

The code intentionally checks carry before using the result. Do not treat a returned AX that resembles a plausible offset as success if carry is set; on error, AX is an error code. Preserve any registers your calling convention requires and ensure interrupts, segment registers, and stack state are valid for the environment from which the call is made.

To place the handle at end-of-file, the origin is 02h and the displacement can be zero:

mov ax, 4202h       ; AH=42h, AL=02h: relative to end of file
xor cx, cx
xor dx, dx
int 21h
jc seek_failed
; DX:AX is the observed file length and the current position is EOF.

This is a length query for a seekable file handle, not a general device-size query. Character devices and unusual redirectors can have different semantics. Check the opened object’s type and the target DOS environment before using the return value as a file length.

Origins and signed movement

Origin 00h is absolute from the start. Use it when the desired byte offset is known and nonnegative. Origin 01h adds the signed displacement to the current position. Origin 02h adds the signed displacement to the end, which is useful for trailers or footer structures when the format definition supports that approach.

For relative origins, do not assume that a negative result will always be rejected immediately. RBIL notes that some DOS implementations permit a pointer to be positioned before the start and report errors only on later I/O; it records a different behavior under Windows NT. Programs should reject negative absolute positions in their own bounds checks before calling the DOS API, even if a particular implementation appears to clamp or reject them.

Use checked arithmetic when converting a record number and record size into a file offset. Multiplication can overflow before the value is placed into CX:DX; compute the product in a wider type or use an explicit overflow check. Validate that the resulting offset is within the format’s allowed range and that the file actually contains the requested record before reading it.

Seeking past EOF and writing

The seek call can position a file pointer beyond the current end without immediately extending the file. RBIL describes a subsequent write as the operation that extends the file. The bytes between the prior end and the write location may be allocated or interpreted according to filesystem and DOS behavior; do not assume a modern filesystem’s sparse-file semantics on FAT.

The write call has its own success conditions. A successful seek does not guarantee that the disk has enough space for a later write. Check both calls independently, validate the number of bytes actually written, and handle a full disk or media error. If a program is creating a structured file, write to a temporary file, verify it, and only then replace the destination using a separately tested rename workflow.

Zero-length writes deserve special care. The DOS write API documents that a zero-byte write can truncate or extend a file to the current position. That is not a harmless “flush” operation. Do not issue AH=40h with CX=0 unless resizing to the current position is explicitly intended and the exact platform behavior has been tested. RBIL also records a DOS 5.0–6.0 bug where a zero-byte extension can appear to succeed despite insufficient disk space; on those kernels, verify the resulting end-of-file instead of trusting carry and AX alone.

File size and compatibility boundaries

The 32-bit CX:DX displacement does not erase DOS’s historical file-size limits. RBIL notes that for FAT32 files, expansion beyond 2 GiB requires an extended open using function AH=6Ch with the extended-size flag. It also records a version-specific FAT-corruption bug when growing a zero-length file directly to a very large size; affected environments should follow the relevant kernel guidance and test with disposable files. The support path varies by DOS kernel and filesystem driver. A program should discover or document the API contract it needs rather than treating every 32-bit offset as universally available.

If the program runs through a DOS extender, a network redirector, or a compatibility layer, its call interface may be mediated. Use that environment’s documented DOS API boundary. Do not mix this real-mode register contract with a protected-mode pointer ABI or assume a Windows host’s lseek semantics automatically apply inside a DOS guest.

Test plan on disposable files

Create a small expendable file with known contents and record its length. Test seeking to byte zero, a valid interior offset, and EOF. Read one byte after each seek and compare it to the expected content. Then test a seek beyond EOF followed by a write on a disposable file, checking the final length and intervening bytes. Repeat only on a filesystem and DOS version supported by the application.

Include negative offsets and invalid handles in error-path tests, but do not run destructive tests against production data. Confirm the carry flag is checked, error codes are preserved, and arithmetic overflow is rejected before the interrupt. Verify that a file-size query either restores the prior position or clearly documents that the handle remains at EOF.

This host has no DOS runtime on which to execute the assembly example. Treat the snippet as a register-contract illustration grounded in the cited API reference; compile and run it in a disposable FreeDOS or compatible DOS test environment before deploying it in a real program.

Operational checklist

Before using AH=42h, confirm the handle is open and seekable, choose the correct origin, form the signed CX:DX displacement in the correct word order, check carry, and validate DX:AX. Handle later I/O separately, especially when seeking beyond EOF. For large files, verify the relevant kernel, filesystem, and extended-open requirements.

File positioning is a state change on a handle. Treat it like one: preserve the previous pointer when necessary, reject impossible offsets in application logic, and never interpret the 32-bit register interface as an unrestricted storage guarantee.

Related:

Sources:

Comments