These two services are how a program reads a directory: DIR uses them, file managers use them, and so does every program that takes *.TXT on its command line, since DOS does not expand wildcards; the program receives "*.TXT" as typed. 4Eh starts a search and returns the first match, 4Fh returns the next one each time it is called, and CF = 1 ends the list.

What the DTA holds

Each match is written to the disk transfer area (see INT 21h AH=1Ah), 43 bytes:

OffsetSizeContents
00h21 bytesDOS's own: the pattern, the drive, the position in the directory. 4Fh continues from here
15hbytethe attribute of the entry found
16hwordthe time: hours × 2048 + minutes × 32 + seconds / 2
18hwordthe date: (year − 1980) × 512 + month × 32 + day
1Ahdwordthe size in bytes, low word first
1Eh13 bytesthe name, "NAME.EXT" and a 0 byte, without padding

The first 21 bytes are the search's state, so 4Fh works only with the same DTA, untouched since the 4Eh. Two searches at once, such as walking a directory tree, need a DTA each, switched with 1Ah before every 4Fh.

Attributes

BitValueThe entry isIn CX
001hread-onlyignored: such files are always found
102hhiddeninclude hidden entries
204hsysteminclude system entries
308hthe volume labelalone: find only the label
410ha directoryinclude subdirectories
520hchanged since the last backup (archive)ignored

CX widens the search rather than filtering it: normal files are always found, and the hidden, system and directory bits add those entries to them. With CX = 10h a search returns files and directories alike, so test bit 4 of the byte at 15h to tell them apart. In a subdirectory the entries "." and ".." come first.

In the pattern, ? matches any one character and * the rest of the name or of the extension. The name and the extension are matched separately, so "*.*" finds everything, names without an extension included, and in MS-DOS "A*B.TXT" is the same as "A*.TXT": whatever follows a * in its part is ignored.

The example lists the current directory with sizes, marking subdirectories. The loop ends when 4Eh or 4Fh return CF = 1:

lists the current directory, one entry per line: the name, then the size in bytes or <DIR> (what it shows depends on the directory)
CLD
MOV AH, 1Ah ; DOS: our own DTA, not the command tail
MOV DX, OFFSET DTA1
INT 21h
MOV AH, 4Eh ; DOS: find the first match
MOV DX, OFFSET PAT1 ; "*.*"
MOV CX, 10h ; files and directories
INT 21h
NEXT1:
JC DONE1 ; CF = 1: no (more) entries
MOV SI, OFFSET DTA1+1Eh ; the name, ending with 0
MOV BX, 13 ; the column is 13 wide
NAME1:
LODSB
OR AL, AL
JZ PAD1
MOV DL, AL
MOV AH, 02h
INT 21h
DEC BX
JMP NAME1
PAD1:
MOV DL, " " ; spaces to the next column
MOV AH, 02h
INT 21h
DEC BX
JNZ PAD1
TEST BYTE PTR DTA1+15h, 10h ; the attribute: a directory?
JZ SIZE1
MOV DX, OFFSET DIRM1
MOV AH, 09h
INT 21h
JMP EOL1
SIZE1:
MOV AX, WORD PTR DTA1+1Ah ; the size, low word
MOV DX, WORD PTR DTA1+1Ch ; and high word
CALL PDEC32
EOL1:
MOV DX, OFFSET CRLF
MOV AH, 09h
INT 21h
MOV AH, 4Fh ; DOS: the next match, same DTA
INT 21h
JMP NEXT1
DONE1:
PAT1 DB "*.*", 0
DIRM1 DB "<DIR>$"
DTA1 DB 43 DUP(0)
CRLF DB 13, 10, "$"
PDEC32 PROC NEAR ; print DX:AX in decimal (changes AX BX CX DX DI)
MOV BX, 10
XOR CX, CX
PDEC32A:
PUSH AX ; DX:AX / 10 in two steps:
MOV AX, DX
XOR DX, DX
DIV BX ; the high word first
MOV DI, AX ; the high word of the quotient
POP AX
DIV BX ; then remainder:low word
PUSH DX ; a digit
INC CX
MOV DX, DI ; DX:AX = the quotient
OR DI, AX
JNZ PDEC32A
PDEC32B:
POP DX
ADD DL, "0"
MOV AH, 02h
INT 21h
LOOP PDEC32B
RET
PDEC32 ENDP

Errors

From 4Eh, CF = 1 with AX = 3 means the path does not exist, and AX = 2 or 18 (12h) that nothing matches: which of the two depends on the DOS version. From 4Fh, AX = 18: no more files. Both are a normal end, not a failure.

Long names in DOSBox-X

The DTA has room for 8.3 names only. A file on a host folder with a longer name shows up under a generated short name such as LONGFI~1.TXT, the same name DIR prints, and that name works with every file service. With long file names enabled, functions 714Eh and 714Fh of INT 21h return long names.