Skip to content

INT 21h/4401h should report invalid function for nonzero DH #266

Open

Description

@nakatamaho

Expected behavior

The MS-DOS 4.0 Programmer's Reference, INT 21h Function 44h, codes 00h/01h requires DH=0 for AL=01h and lists AX=1 when AL=01h is called with DH nonzero.

Current source behavior

At lpproj/fdkernel commit ac16c8a7401526f99787e03babdb2f4223d48fc4, DosDevIOctl() returns DE_INVLDDATA for a nonzero DH in the AL=01h path. This exposes error 13 instead of the documented error 1.

Please confirm whether FreeDOS intentionally differs from the documented DOS contract here. If not, consider returning invalid function for this invalid AL/DH combination. The conformance fixture keeps the reference-based AX=1 expectation.

The comparison uses only the public source revision and public reference; it contains no private media or trace data.

Activity

  1. andrewbird commented on Oct 8, 2026

    @andrewbird
    Contributor

    It seems a295a75f964 set it that way, unfortunately this repo doesn't have the full history but FDPP does (dosemu2/fdpp@a295a75)

    git show a295a75
    commit a295a75f964db28a0e8f58e4682c6ea5b5e61d37
    Author: bartoldeman <bartoldeman@6ac86273-5f31-0410-b378-82cca8765d1b>
    Date:   Tue Apr 13 20:37:30 2004 +0000
    
        Correct int21/ax=4401: returned AL = old value, should set CY and DE_INVLDDATA
        if DH!=0 (RBIL is a little confusing here).
        
        
        git-svn-id: https://svn.code.sf.net/p/freedos/svn/kernel/trunk@878 6ac86273-5f31-0410-b378-82cca8765d1b
    
    diff --git a/kernel/ioctl.c b/kernel/ioctl.c
    index 2b314bbc..cb1334d2 100644
    --- a/kernel/ioctl.c
    +++ b/kernel/ioctl.c
    @@ -106,10 +106,17 @@ COUNT DosDevIOctl(lregs * r)
               /* can't set the status of a file.                      */
               if (!(flags & SFT_FDEVICE))
                 return DE_INVLDFUNC;
    +          /* RBIL says this is only for DOS < 6, but MSDOS 7.10   */
    +          /* returns this as well... and some buggy program relies*/
    +          /* on it :(                                             */
    +          if (r->DH != 0)
    +            return DE_INVLDDATA;
     
    +          /* Undocumented: AL should get the old value            */
    +          r->AL = s->sft_flags_lo;
               /* Set it to what we got in the DL register from the    */
               /* user.                                                */
    -          r->AL = s->sft_flags_lo = SFT_FDEVICE | r->DL;
    +          s->sft_flags_lo = SFT_FDEVICE | r->DL;
               break;
     
             case 0x02:

    RBIL doesn't document the error code to be used. https://fd.lod.bz/rbil/interrup/dos_kernel/214401.html#sect-2861

    The document linked to earlier isn't explicit about which error code is returned on DH != 0. I suspect we need to test various DOSes to confirm.

    ────────────────────────────────────────────────────────────────────────────
    IOCtl Data (Function 44H, Codes 0 and 1)
    ────────────────────────────────────────────────────────────────────────────
    
        Call:
    
        AH = 44H
        AL
            0 = Get device data
            1 = Set device data
        BX
            Handle
        DX
            Device data (see text)
    
        Return:
    
        Carry set:
        AX
            1 = Invalid function
            6 = Invalid handle
        Carry not set:
        DX
        Device data
    
    
        Comments:
    
        Function 44H, Codes 0 and 1, either gets or sets the data MS-DOS uses to
        control the device. AL must contain 0 to get the data or 1 to set it. BX
        must contain the handle. If AL is 1, DH must contain 0.
    
  2. andrewbird commented on Oct 8, 2026

    @andrewbird
    Contributor

    Checking the MS-DOS v4 source, it seems to be correct as is, but could do with a second opinion?
    https://github.com/microsoft/MS-DOS/blob/2d04cacc5322951f187bb17e017c12920ac8ebe2/v4.0/src/DOS/IOCTL.ASM#L247-L271

    	procedure   $IOCTL,NEAR
    	ASSUME	DS:NOTHING,ES:NOTHING
    	MOV	SI,DS			; Stash DS for calls 2,3,4 and 5
    	context DS
    	CMP	AL,3
    	JBE	ioctl_check_char	; char	device
    	JMP	ioctl_check_block	; Block device
    ioctl_check_char:
    	invoke	SFFromHandle		; ES:DI -> SFT
    	JNC	ioctl_check_permissions ; have valid handle
    ioctl_bad_handle:
    	error	error_invalid_handle
    
    
    ioctl_check_permissions:
    	CMP	AL,2
    	JAE	ioctl_control_string
    	CMP	AL,0
    	MOV	AL,BYTE PTR ES:[DI.sf_flags]; Get low byte of flags
    	JZ	ioctl_read		; read the byte
    	PUSH	DX			;AN000;MS.
    	AND	DH,0FEH 		;AN000;MS.allow DH=01H
    	POP	DX			;AN000;MS.
    	JZ	ioctl_check_device	; can I set with this data?
    	error	error_invalid_data	; no DH <> 0
    

    However my 'DOS and BIOS functions quick reference' (QUE) says it should be AX=1, it's here https://archive.org/details/dosbiosfunctions0000unse/page/112/mode/2up

  3. ecm-pushbx commented on Oct 8, 2026

    @ecm-pushbx
    Contributor

    DH equal to 1 is explicitly commented and coded as allowed by this MS-DOS code, and is not a no-op. This is bit 8 (mask 100h) in the device info word

    That bit is not defined in the list nor in the FreeDOS header:

    kernel/hdr/sft.h

    Lines 108 to 132 in d6791ad

    /* flag bits */
    /* the following bit is for redirection */
    #define SFT_FSHARED 0x8000 /* Networked access */
    /* the following entry differntiates char & block access */
    #define SFT_FDEVICE 0x0080 /* device entry */
    /* the following bits are file (block) unique */
    #define SFT_FDATE 0x4000 /* File date set */
    #define SFT_FFIXEDMEDIA 0x0800 /* File on non-removable media - unused */
    #define SFT_FCLEAN 0x0040 /* File has not been written to */
    #define SFT_FDMASK 0x003f /* File mask for drive no */
    /* the following bits are device (char) unique */
    #define SFT_FIOCTL 0x4000 /* IOCTL support - device */
    #define SFT_FOCRM 0x0800 /* Open/Close/RM bit in device attribute*/
    #define SFT_FEOF 0x0040 /* device eof */
    #define SFT_FBINARY 0x0020 /* device binary mode */
    #define SFT_FSPECIAL 0x0010 /* int 29 support */
    #define SFT_FCLOCK 0x0008 /* device is clock */
    #define SFT_FNUL 0x0004 /* device is nul */
    #define SFT_FCONOUT 0x0002 /* device is console output */
    #define SFT_FCONIN 0x0001 /* device is console input */

    Perhaps MS-DOS v5 or v6 changed this code to prohibit DH equal to 1? Should be easy enough to test.

    However, it is used by MS-DOS (at least v4.00, v4.01, and the lMS-DOS kernel based on those) in exactingly one spot: https://hg.pushbx.org/ecm/tlsfiles/file/00e7de020891/msdos4/msbio.tls#l100604 (source text at https://hg.pushbx.org/ecm/msdos4/file/31d10ff75cb8/src/DOS/disk3.nas#l649 in current lDOS). And it is defined in the sf.mac file: https://hg.pushbx.org/ecm/msdos4/file/31d10ff75cb8/src/INC/sf.mac#l106

  4. ecm-pushbx commented on Oct 8, 2026

    @ecm-pushbx
    Contributor

    Regardless the exact behaviour, it warrants an update to the list. Do you want to prepare a pull request, or should I do so?

    The disk3.nas code probably should be studied in more detail to figure out when it is called and what it does.

  5. andrewbird commented on Oct 8, 2026

    @andrewbird
    Contributor

    I'm not good at words, so I'm sure it'll be better if you do it. Thanks.

  6. ecm-pushbx commented on Oct 8, 2026

    @ecm-pushbx
    Contributor

    Interestingly, the list does document that flag, but only for disk file handles and only in function 4400h, not in the SFT layout: https://github.com/LoopZ/TheList/blob/ff4a52aa/source/Interrupt%20List/INT%2021%20DOS%20Function%20Calls/INT%20214400%20DOS%202%20IOCTL%20GET%20DEVICE%20INFORMATION.txt

    Bitfields for device information word:
    Bit(s)	Description	(Table 01423)
     character device
      14	device driver can process IOCTL requests (see AX=4402h"DOS 2+")
      13	output until busy supported
      11	driver supports OPEN/CLOSE calls
       8	??? (set by MS-DOS 6.2x KEYB)
       7	set (indicates device)
       6	EOF on input
       5	raw (binary) mode
       4	device is special (uses INT 29)
       3	clock device
       2	NUL device
       1	standard output
       0	standard input
     disk file
      15	file is remote (DOS 3.0+)
      14	don't set file date/time on closing (DOS 3.0+)
      11	media not removable
       8	(DOS 4 only) generate INT 24 if no disk space on write or read past
    	  end of file
       7	clear (indicates file)
       6	file has not been written
      5-0	drive number (0 = A:)
    SeeAlso: INT 29
    

    Testing on MS-DOS v5 reveals that indeed it doesn't allow to set this bit, it checks for DH exactly equal to zero.

  7. ecm-pushbx commented on Oct 8, 2026

    @ecm-pushbx
    Contributor

    For the set function 4401h, the list tells us it is only valid for character devices and that "(DH must be zero for DOS version prior to 6.x)". However, this "version prior to v6" is apparently valid only for v5. v4 does allow DH = 1 and only uses DH for disk file handles, ignoring DL on those.

    Reference: https://github.com/LoopZ/TheList/blob/ff4a52aa8cefb82b02ceada68ef4d8f4647c3fb2/source/Interrupt%20List/INT%2021%20DOS%20Function%20Calls/INT%20214401%20DOS%202%20IOCTL%20SET%20DEVICE%20INFORMATION.txt

  8. ecm-pushbx commented on Oct 8, 2026

    @ecm-pushbx
    Contributor

    MS-DOS v7.10 seems to use the same code as v5.

    It only returns DH = AH = 00h, DL = AL = SFT flags for 4400h on disk file handles, DH = AH = from device attributes high byte, DL = AL = SFT flags for 4400h on character device handles.

    On function 4401h, error 06h is returned if not a valid handle at all, error 0Dh if DH != 0 on input, error 01h if the handle refers to a disk file, and after that the low byte of SFT flags is set to DL | 80h (character device bit is forced set). Returned AL in case of no errors is prior SFT flags.

    I wonder where the disk file bits 11, 14, 15 made it into the function 4400h description in the list. Out of MS-DOS v4.00, v4.01, v5, v7.10, all of them always return the high byte in AH and DH as all-zeroes for a disk file.

  9. ecm-pushbx commented on Oct 8, 2026

    @ecm-pushbx
    Contributor

    Added an initial commit to the interrupt list in LoopZ/TheList#121

  10. andrewbird commented on Oct 8, 2026

    @andrewbird
    Contributor

    Added an initial commit to the interrupt list in LoopZ/TheList#121

    And so much better than I could have written, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions