FreeDOS INT 24h: Handling DOS Critical I/O Errors Safely
Understand the DOS critical-error callback, its input registers and return actions, and why an INT 24h handler must remain short and conservative.
INT 24h is a DOS callback, not a function an application calls to ask whether a disk is healthy. DOS transfers control to the current critical-error handler when an I/O request fails in a way that needs a policy decision. Typical cases include a not-ready removable drive, write protection, a failed sector operation, or a printer/device error. The handler chooses what DOS should do with the operation already in progress; it does not repair the hardware, retry at the application protocol level, or replace ordinary error checking on INT 21h calls.
This callback runs while DOS is servicing another request. That context is the most important design constraint: DOS’s internal state and stack are active, and most DOS services are not safe to re-enter. An INT 24 handler must be short, preserve the specified machine state, and return through the interrupt frame promptly. Treat it more like a constrained recovery decision point than a general-purpose exception framework.
Decode the inputs before choosing an action
On entry, AH carries the error class and processing flags. For a block-device error, bit 7 is clear and AL identifies the drive number. For a character-device error, bit 7 is set; BP:SI points to the device header and DI’s low byte contains the error code. The device header is useful context, but avoid modifying it. The bit layout documented by the historical DOS interface is:
AH bits |
Meaning |
|---|---|
| 7 | Error class: disk/block path when clear; character-device class when set |
| 5, 4, 3 | DOS 3.0+ permission flags for Ignore, Retry, and Fail respectively |
| 2–1 | For disk errors, area: DOS, FAT, root directory, or data area |
| 0 | For disk errors, set for write and clear for read |
Do not infer the specific device operation from the area bits alone. DI contains the critical-error code when the character-device flag is set; traditional codes include write protection, drive-not-ready, CRC/data error, seek error, printer out of paper, write fault, read fault, and general failure. DOS versions and redirectors added codes, so a handler should preserve the raw value and avoid displaying a misleading fixed translation table unless it knows the operating-system version.
The error code and permission bits answer different questions. The code describes what failed; the flags indicate which responses DOS permits for that event. Abort is always allowed in the documented DOS contract. On DOS 3 and later, Ignore or Retry requested when disallowed is converted to Fail, and a disallowed Fail is converted to Abort. DOS 3.1+ also converts Ignore to Fail for network critical errors. A callback must not assume the action it requested will be the action DOS actually applies.
Select a response with data integrity in mind
The return action is placed in AL before IRET:
00hIgnore: continue the request as though the error had not stopped it. This is dangerous for a failed write because the caller may believe data reached storage when it did not.01hRetry: ask DOS/device processing to attempt the operation again. Use only when the condition may have been corrected, such as inserting the requested disk. An endless retry loop is not a recovery policy.02hAbort: terminate the current program through DOS’s termination path. It is not the same as returning an application-specific error code.03hFail: fail the DOS call in progress; available on DOS 3 and later. The caller must still check the normal call result and decide what to report or roll back.
When uncertainty exists, Fail is usually safer than Ignore because it returns control to the caller without pretending that the requested I/O succeeded. Whether a Retry is sensible depends on the operation and the device state. For example, retrying after reseating a removable medium may be useful; repeatedly retrying a persistent media fault can stall the user and increase risk. A handler cannot know whether an application has already committed related state, so it should not invent an automatic multi-step recovery transaction.
Keep the routine small and preserve the contract
The classic DOS documentation is unusually restrictive about re-entrancy. Ralf Brown’s Interrupt List records the safe INT 21h subset as functions 01h–0Ch, 30h, and 59h; even these should be used only if the implementation needs them and the exact target supports them in this context. File opens, closes, reads, writes, allocation, process execution, and shell commands are not suitable inside the callback. Do not call INT 24h yourself to test a condition; it is invoked by DOS as part of error processing.
Preserve SS:SP, DS, ES, BX, CX, and DX as required by the historical contract, and keep stack use bounded. The interrupt return frame belongs to DOS’s active request. Returning with the wrong stack depth, changing the saved state, or trying to jump back into a foreground path can leave DOS unstable. In particular, a specialized handler that removes DOS frames and returns directly to the application is an advanced, version-sensitive technique; the documented warning is that DOS may remain unstable until a later DOS call above AH=0Ch. This guide deliberately recommends the ordinary action-code path instead.
Install and restore the vector through DOS rather than writing the interrupt table without ownership discipline. INT 21h/AH=35h obtains the prior vector (AL=24h, returned in ES:BX) and AH=25h installs a vector (AL=24h, DS:DX points to the handler). A resident component must keep its handler and saved-vector data resident for as long as the hook is active, restore the previous handler before unloading, and chain or coordinate with other handlers according to their documented interface. An application that temporarily installs a handler must restore it on every exit path.
Test only controlled failure cases
Use a disposable virtual disk or removable test image, never the only copy of a real disk. Record the kernel version, filesystem, driver, operation, and return action. Exercise a known write-protect or missing-media case only when the test medium can safely fail; confirm that Retry occurs once after the condition is fixed and that Fail reaches the caller as an error. Also test a character-device failure separately from a block-device failure, because the input fields differ. Verify that a callback does not deadlock, corrupt registers, or leak its resident memory after being removed.
Keep application recovery outside the handler. The application should inspect carry/error returns, decide whether its own higher-level transaction can be retried, and tell the user what data may not have been committed. INT 24h is valuable precisely because it offers a narrow point to choose how DOS proceeds; respecting that narrow contract makes it useful without turning it into unsafe DOS re-entry.
An Abort response should not be treated as a normal return path where the application is guaranteed to run its own cleanup code. Keep user-visible state, open files, and temporary-file strategy in the foreground program’s design. If the handler is installed by a TSR, document its lifetime and interaction with other resident software; a stale vector into freed memory is a machine-wide failure, not merely a bad error message. Test install, chain, restore, and unload behavior as carefully as the chosen error response.
Related:
- Understanding Interrupts on FreeDOS: INT 21h and the DOS API
- Device Drivers on FreeDOS: How .SYS Files Extend the Kernel
Sources: