Getting large drive structure information in DOS 7.x

5

I wrote a directory information utility and (because I, and the people I wrote this for collect & use vintage hardware,) made it compatible with DOS and Windows 9x as well as Windows XP/Vista/7/8 64-bit (because we also use those.) The problem I ran into was with Windows 9x and FAT32 drives. I managed to get it to work as long as Windows 9x was actually loaded, but if I boot to command prompt only, or restart in MS-DOS mode I lose access to the Windows API that allowed me to get the large drive data and it defaulted back to the DOS routine I have. These are limited to the 2GB limit routines. Examining how the DOS 7.x programs (chkdsk mainly,) handle this (as they have no problem reporting the correct drive sizes,) it seems they use DOS interrupts (primarily INT 21h,) to do this. Thinking, no problem, I'll do a quick version check, and if it's DOS 7 or higher I'll just run a quick assembly routing to get the drive structure and calculate the total & free space that way. Only, the routine (though it doesn't return an error,) doesn't fill my buffer with anything.

Here is the code:

#include <stdio.h>
#include <dos.h>

void main(void) {
    unsigned short hes,hdi,sectors,bytes;
    unsigned long tclusters,fclusters;
    unsigned char far *drivedata;
    char test = '\0';
    char display[17] = "0123456789ABCDEF";
    int count;

    drivedata = new unsigned char [63];

    for (count = 0; count < 63; count++) drivedata[count] = '\0';

    drivedata[0] = '\x3d';
    drivedata[1] = '\x00';

    hes = FP_SEG(drivedata);
    hdi = FP_OFF(drivedata);

asm {
        push ax
        push es
        push di
        push ds
        push dx
        push cx
        mov ax,0x440d
        mov bx,0x0003
        mov cx,0x484a
        int 21h
        jnc _GOOD
        mov ax,0x7302
        mov es,[hes]
        mov di,[hdi]
        mov dx,0x0003
        mov cx,0x003f
        int 21h
        jnc _GOOD
    }
    test = '\1';
_GOOD:
    asm {
        mov ax,0x440d
        mov bl,0x03
        mov cx,0x486a
        int 21h
        pop cx
        pop dx
        pop ds
        pop di
        pop es
        pop ax
    }

    if (test == '\1') {
        printf("There was an error.\r\n");
        return;
    }



    tclusters = (unsigned long) drivedata[48];
    tclusters = (tclusters * 256) + (unsigned long)drivedata[47];
    tclusters = (tclusters * 256) + (unsigned long)drivedata[46];
    tclusters = (tclusters * 256) + (unsigned long)drivedata[45];
    ++tclusters;

    fclusters = (unsigned long)drivedata[36];
    fclusters = (fclusters * 256) + (unsigned long)drivedata[35];
    fclusters = (fclusters * 256) + (unsigned long)drivedata[34];
    fclusters = (fclusters * 257) + (unsigned long)drivedata[33];

    bytes = (unsigned int)drivedata[5];
    bytes = (bytes * 256) + (unsigned int)drivedata[4];

    sectors = (unsigned long)drivedata[6];
    ++sectors;

    printf("Drive C has:\r\n");
    printf("   Total Clusters: %u\r\n",tclusters);
    printf("    Free Clusters: %u\r\n",fclusters);
    printf("          Sectors: %u\r\n",sectors);
    printf("            Bytes: %u\r\n",bytes);

    printf("\r\n");
    printf("   |  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n");
    printf("---------------------------------------------------------------------");
    for (count = 0; count < 63; count++) {
        if ((count % 16) == 0) printf("\r\n %c | ",display[(count / 16)]);
        printf("%03u ",drivedata[count]);
    }
    printf("\r\n");

    return;
}

That last bit was me trying to figure out what was going wrong. I was getting strange results, and couldn't figure out a pattern. Originally, I wasn't worried about clearing the buffer as the INT call should have been filling it with it's own values (except the first 2 bytes, which is supposed to be filled with the EDB data buffer size.) After getting so many apparently random results, I added in the loop at the beginning to fill the buffer with zeroes, then add in the buffer size. The results stopped being random at that point, they were invariably all zeroes, meaning the INT call isn't filling the buffer. With a variety of tests, I've confirmed that hes & hdi are correctly being assigned the segment and offset of the buffer address. I've also tried es & di to the pointer address instead of the buffer address. I didn't think it would work as everything I read said to set it to the address and not to a pointer, but I was trying everything I could think of. In all cases, the buffer isn't getting filled with anything.

As you can probably tell, this is just a test program I'm writing to figure out the exact procedure before adding it to my main program (which works just fine except for this one issue.) The FP_ lines are just macros that could be stated as (unsigned long)(x & 0xffff0000) >> 16 for the segment and (unsigned long)(x & 0x0000ffff) for the offset. Normally you would pass the pointer (&drivedata,) but drivedata is already a pointer.

The actual output:

Drive C has:
   Total Clusters: 1
    Free Clusters: 0
          Sectors: 1
            Bytes: 0

   |  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
---------------------------------------------------------------------
 0 | 061 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 
 1 | 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 
 2 | 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 
 3 | 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 

So, what am I missing? Like chkdsk, I'm locking the drive before and unlocking it after the call (though I'm not sure of the necessity.) How can I get this to work right? Alternately, is there a better way of getting the drive structure (clusters, sectors per cluster, bytes per sector,) than using INT 21h? Everything I find in searches only point me to Windows API functions, which the user won't have access to, if they do a boot to command prompt etc...

c++
assembly
dos
asked on Stack Overflow Sep 8, 2014 by user3399848 • edited Mar 12, 2018 by too honest for this site

2 Answers

1

Wow, using DOS, that's old-school! Not as old-school as using punch cards, but still...

Apparently, FreeDOS has FAT 32 support. You might try to install it on those machines that don't even have Windows 95 installed.

answered on Stack Overflow Sep 8, 2014 by zmbq
1

For your vintage hobby you should equip yourself with the LBA and FAT32 specifications, Wikipedia: File Allocation Table seems to have good links.

One thing you may find out is that those legacy systems (and software written for them) could not handle large disks (disk size > 2^(32-1)) gracefully.

Other material I think would be also very important:

The "better way" that should work for you in all cases would be to use BIOS calls to find out all the basics and then replicate algorithms for calculating the sizes etc. in your own code. In the old DOS days there was no easily reusable API available for non-Microsoft programs. Programs that needed to do advanced things had to know how to do it bare-bone by themselves..

answered on Stack Overflow Sep 9, 2014 by xmojmr

User contributions licensed under CC BY-SA 3.0