GetDiskFreeSpaceEx

DOS specific questions.
MJK
Posts: 179
Joined: Nov 08, 2005 17:14
Location: Dublin, Ireland
Contact:

Re: GetDiskFreeSpaceEx

Postby MJK » Sep 17, 2013 20:22

FIVE years later ;-)

I bumped (?) into this thread, downloaded the FREE.BAS code (Thank You, DOS386), played with it, merged in a few bits of ASM code I had already written, made a few mods... Here's the result, in case it's useful to any lurkers:

Code: Select all

'--------------------------------------------------------------------
' ### DOS GetDiskFreeSpaceEx example (R) ###

' (CL) 2008-07-24 by DOS386 | Public Domain | ABUSE at your own risk !!!

' Mods by MJK (Mike Kennedy), Sept 2013:
'   - Detect Int-31/Int-21 errors
'   - Some additional debugging/tracing options
'   - Modded bits of the ASM code

' "FREE.BAS" , COMPILE WITH FBC 0.20 to DOS target

' !!! DOS only !!! | Requires a recent DOS kernel (FreeDOS, EDR-DOS, RX-DOS)

' Uses INT $31/$0300, works with any usable DPMI host (D3X, HDPMI32, ...)

'--------------------------------------------------------------------

    #include "dos/go32.bi"                                  'DPMI stuff declarations

    type UINT64  as ULONGINT
    type UINT32  as UINTEGER
    type UINT16  as USHORT
    type UINT8   as UBYTE

    Declare Sub GDFSEX (BYVAL AS UINT32, BYVAL AS UINT32, BYVAL AS UINT32, BYVAL AS UINT32, _
                        BYVAL AS UINT32, BYVAL AS UINT32, BYVAL AS UINT32, BYVAL AS UINT32)

    Declare Sub Dump_Buffer(LTB1() as UINT8, ByRef Fl_Nam1 as string, ByRef Fil_Nam2 as string)

    DIM SHARED AS UINT32 DDS, LTB

    DIM AS STRING  VGSCOMMAND
    DIM AS UINT64  VGN64FREE, VGN64TOTAL
    DIM AS UINT32  SZ1, SZ2, P1, P2, P3, P4, P5, P6, P7, P8
    DIM AS UINT32  A1, A2, A3, A4, A5
    DIM AS UINT16  i, f, Debug_Opt, Int31_Err, Int21_Err1, Int21_Err2
    DIM AS UINT8   INFOBUF (0 TO 64)
    DIM AS UINT8   LTB1    (0 TO 2047)
    DIM AS UINT8   LTB2    (0 TO 2047)

    ' ************************************************************

    ?
    ? "FREE.BAS | FREE.EXE | GetDiskFreeSpaceEx | (CL) 2008-07-24"
    ?
    ? "Usage: ""FREE D:\"""
    ?

    Debug_Opt = 1                           '0 = No Debugging, else Debug

    VGSCOMMAND=UCASE$(COMMAND$(1))
    IF (VGSCOMMAND<>"\") AND (LEN(VGSCOMMAND)<2) THEN VGSCOMMAND="."  'Default drive, "." or "\" (might not work with every DOS)

    ? "Looking for total & free space in """+VGSCOMMAND+""" :-D"
    ?

    VGSCOMMAND += Chr(0)

    ' Init GO32

    DDS = _go32_info_block.selector_for_linear_memory
    LTB = _go32_info_block.linear_address_of_transfer_buffer
    SZ1 = _go32_info_block.size_of_this_structure_in_bytes      'Seeing 40 bytes here
    SZ2 = _go32_info_block.size_of_transfer_buffer              '>4k, seeing 16KB

    ? "Size of DDS Structure: ";SZ1
    ? "Size of LTB Buffer   : ";SZ2
    ?

    ? "Calling the INT ... " ;

    P1 = CAST(UINT32,STRPTR(VGSCOMMAND))    'Source spec
    P2 = CAST(UINT32,VARPTR(INFOBUF(0)))    'Dest buffer
    P3 = CAST(UINT32,VARPTR(Int31_Err))     'Int-31 Error-code
    P4 = CAST(UINT32,VARPTR(Int21_Err1))    'Int-21 Error-code (Main)
    P5 = CAST(UINT32,VARPTR(Int21_Err2 ))   'Int-21 Error-Code (supplementary)
    P6 = CAST(UINT32,VARPTR(Debug_Opt))     '0 = No Debug, else Debug-Trace
    P7 = CAST(UINT32,VARPTR(LTB1(0)))       'Int-31 Buf (before Int-31)
    P8 = CAST(UINT32,VARPTR(LTB2(0)))       'Int-31 Buf (after Int-31)

    GDFSEX (P1,P2,P3,P4,P5,P6,P7,P8)        'Get-Disk-Free-Space-EXtended

    ? "done !!!" : ?

    ? "Errors-Codes: ";Int31_Err ;" [0x";Hex(Int31_Err ,4);"], "; _
                       Int21_Err1;" [0x";Hex(Int21_Err1,4);"], "; _
                       Int21_Err2;" [0x";Hex(Int21_Err2,4);"]"
    ?

    If Debug_Opt Then
        Dump_Buffer(LTB1(), "LTB_Buf1a.TMP", "LTB_Buf1b")
        Dump_Buffer(LTB2(), "LTB_Buf2a.TMP", "LTB_Buf2b")
    EndIf

    A1 = INFOBUF( 0) + ( INFOBUF( 1) SHL 8 ) 'SUCCESS / SIZE
    A2 = INFOBUF( 4) + ( INFOBUF( 5) SHL 8 ) 'SEC/CLU
    A3 = INFOBUF( 8) + ( INFOBUF( 9) SHL 8 ) 'BYT/SEC
    A4 = INFOBUF(28) + ( INFOBUF(29) SHL 8 ) + ( INFOBUF(30) SHL 16 ) + ( INFOBUF(31) SHL 24 ) 'FREE CLU
    A5 = INFOBUF(32) + ( INFOBUF(33) SHL 8 ) + ( INFOBUF(34) SHL 16 ) + ( INFOBUF(35) SHL 24 ) 'TOTAL CLU

    ? "Results (not reliable if error-codes are non-zero):"
    ? "  Success/Size (>0) : ";A1
    ? "  Sec/Clu (1...64)  : ";A2
    ? "  Byt/Sec (512)     : ";A3
    ? "  Clust free        : ";A4
    ? "  Clust total       : ";A5

    IF (A1<>0) THEN

        VGN64FREE  = CAST(UINT64,(A2 * A3))
        VGN64TOTAL = VGN64FREE
        VGN64FREE  = VGN64FREE  * A4
        VGN64TOTAL = VGN64TOTAL * A5

        ? : ? "Bytes free  : " ; VGN64FREE
        If VGN64FREE>&H0400       THEN ? "KiB free    : " ; (VGN64FREE Shr 10)
        If VGN64FREE>&H0100000    THEN ? "MiB free    : " ; (VGN64FREE Shr 20)
        If VGN64FREE>&H040000000  THEN ? "GiB free    : " ; (VGN64FREE Shr 30)

        ? : ? "Bytes total : " ; VGN64TOTAL
        If VGN64TOTAL>&H0400      THEN ? "KiB total   : " ; (VGN64TOTAL Shr 10)
        If VGN64TOTAL>&H0100000   THEN ? "MiB total   : " ; (VGN64TOTAL Shr 20)
        If VGN64TOTAL>&H040000000 THEN ? "GiB total   : " ; (VGN64TOTAL Shr 30)

    ENDIF

    ? "Done !!!" : ?
    sleep

    END

'***************************************************************************************************

Sub Dump_Buffer(LTBn() as UINT8, ByRef Fl1 as string, ByRef Fl2 as string)

    Dim as integer f, i, j
    Dim as string  x

    f = FREEFILE: Open Fl1 for output as #f
    For i=0 to 2047: Print #f,Chr(LTBn(i));: next i
    Close #f

    f = FREEFILE: Open Fl2 for output as #f
    i = 0
    Do
        x = ""
        Print #f,Hex(i,4);": ";

        for j=1 to 8
            Print #f,Hex(LTBn(i),2);" ";: If LTBn(i) < 32 Then x += "." else x += Chr(LTBn(i))
            i += 1
        Next j
        print #f," ";

        for j=1 to 8
            Print #f,Hex(LTBn(i),2);" ";: If LTBn(i) < 32 Then x += "." else x += Chr(LTBn(i))
            i += 1
        Next j

        Print #f,x
    Loop while i <= 2047

End Sub

'***************************************************************************************************
' GetDiskFreeSpaceEx - INT $21/$7303 | src: [DS:DX] | dest:[ES:DI]
'
' IN:  addr of spec: "C:\" or "." (12 max), addr of buffer (64 bytes)
' OUT: nothing / buffer filled
'
' Memory:   0: $32 bytes INT $31  | $32: $0E bytes wasted
'         $40: $10 bytes src spec | $50: $40 bytes dest buffer / struct
'         $90: 1.5 KiB stack
'
' Uses globals: LTB (linear addr of buffer) and DDS (ZERO-based selector)
'
' Parms: Address of src spec string (<=12 chars), address of dest buffer (64 bytes),

SUB GDFSEX (BYVAL VLN32SPEC AS UINT32, BYVAL VLN32DEST AS UINT32, _
            BYVAL I31_Err_Addr AS UINT32, BYVAL I21_Err1_Addr AS UINT32, BYVAL I21_Err2_Addr AS UINT32, _
            BYVAL Debug_It as UINT32, LTB1_Buf AS UINT32, BYVAL LTB2_Buf as UINT32)

    ASM
            push  ds                    'Preserve
            push  es

            mov   eax,[DDS]             'To clear (2 KB) and INT (based on ES, size=$32)
            test  eax,0xFFFF0000        '(We Expect all Hi-Bits to be 0)
            jnz   x0
            push  eax                   'Uses FOUR bytes
            pop   es                    'Uses FOUR bytes
            cld

            mov   edi,[I31_Err_Addr]    'Clear Error-Codes (access via DS)
            mov   word ptr [edi+0],0
            mov   edi,[I21_Err1_Addr]
            mov   word ptr [edi+0],0
            mov   edi,[I21_Err2_Addr]
            mov   word ptr [edi+0],0

            ' Clear the 2KB "Linear Transfer Buffer"

            mov   edi,[LTB]
            xor   eax,eax               'For DPMI simulant INT $31 and clearing
            mov   ecx,512
        rep stosd                       '[ES:EDI] ZERO'izing 2 KB | side effect: ECX==0

            ' Move in the "Drive"-spec string to the LTB (at offset 64)

            mov   edi,[LTB]
            add   edi,0x40
            mov   esi,[VLN32SPEC]
            movsd                       '[ES:EDI]<-[DS:ESI] | ESI and EDI trashed
            movsd
            movsd                       '12 bytes spec size max

            ' LTB Layout:
            ' Command-Parms:
            '  $000-003  DWORD  EDI
            '  $004-007  DWORD  ESI
            '  $008-00B  DWORD  EBP
            '  $00C-00F  DWORD  reserved (0)
            '  $010-013  DWORD  EBX
            '  $014-017  DWORD  EDX
            '  $018-01B  DWORD  ECX
            '  $01C-01F  DWORD  EAX
            '  $020-021  WORD   flags
            '  $022-023  WORD   ES
            '  $024-025  WORD   DS
            '  $026-027  WORD   FS
            '  $028-029  WORD   GS
            '  $02A-02B  WORD   IP
            '  $02C-02D  WORD   CS
            '  $02E-02F  WORD   SP
            '  $030-031  WORD   SS
            '  $032-03F  Filler (14 bytes)
            '
            '  $040-04B  Drive-ID string, eg "C:\",00,00,00...
            '  $04C-04F  Filler (4 bytes)
            '
            '  $050-08F  Buffer for Int-21-7303...
            '
            '  $090-68F  Int-21 Stack
            '  $690-7FF  Spare?

            ' Build 7303 parms...
            '   DS:DX -> ASCIZ string for drive ("C:\" or "\\SERVER\Share")
            '   ES:DI -> buffer for extended free space structure (see #01789)
            '   CX = length of buffer for extended free space (can be cca 36...44 bytes)
            ' Return:
            '   CF clear if successful ||| Buffer (at ES:DI) filled
            '   CF set on error        ||| AX = error code

            mov   edi,[LTB]

            mov   eax,0x7303        '% GetDiskFreeSpaceEx
            mov   es:[edi+0x1C],eax '% EAX / AX = $7303
 
            mov   eax,48            '& Expected buffer size (expected min = 36, max = 44)
            mov   es:[edi+0x18],al  '& CL / CX / ECX
 
            mov   eax,edi           '@ Linear - Calculate starting Seg-Addr
            shr   eax,4             '@ To segment | high 16 bits==0 | Round Down
            mov   es:[edi+0x24],ax  '@ DS Seg-addr - to access the "C:\" spec
            mov   es:[edi+0x22],ax  '@ ES Seg-addr - to access the buffer
            mov   es:[edi+0x30],ax  '@ SS
 
            mov   eax,edi           '@ Linear - Calculate offsets
            and   eax,0x0000000F    '@ Just the lowest 4 bits
            add   eax,0x40
            mov   es:[edi+0x14],ax  '@ [DS:] DX Offset - to access the "C:\" spec
 
            add   eax,0x10          '@ The Buffer (64 bytes)
            mov   es:[edi+0x00],ax  '@ [ES:] DI
 
            add   eax,0x40          '@ Start of the Stack (0x0090)
            add   eax,0x0600        '& End of the stack (1536 bytes, 0x0600)
            mov   es:[edi+0x2E],ax  '& SP

            ' Make a debug copy of the main LTB buffer (before Int-31)

            mov   esi,[Debug_It]    'Debugging?
            mov   ecx,[esi+0]
            jcxz  d1                'No
            mov   esi,[LTB]
            mov   edi,[LTB1_Buf]    'Our "Destination"
            mov   ecx,512           '2048 BYTES (0-2047)
            push  ds                'Swap DS/ES
            push  es
            pop   ds
            pop   es
        rep movsd                   'Move all bytes, DS:ESI to ES:EDI
            push  ds                'Restore DS/ES
            push  es
            pop   ds
            pop   es
            mov   edi,[LTB]         'Restore EDI
        d1:

            ' Build Int-31 parms

            mov   eax,0x0300        '@ For DPMI simulant INT $31 / $0300 see far below
            mov   ebx,0x21          '@ BL=$21
            mov   bh,0              '@ BH=0 (crap flags)
        ''  mov   bh,1              '@ BH=0 (crap flags)
            xor   ecx,ecx           '# Words to copy from PM (Prot-Mode) stack to RM (Real-Mode) stack

            pushf                   'Uses FOUR bytes!!
            pop   edx               'Uses FOUR bytes
        ''  mov   es:[edi+0x20],dx  'FLAGS 16-bit only, we poke 16-bits
            mov   es:[edi+0x20],dl  'FLAGS 16-bit only, we poke only 8-bits

            int   0x31              'AX==$0300, BX==$21, CX, ES:DI=Selector:Offset of RM struct
            jc    x1

            ' Reset some Regs...

            mov   edi,[LTB]         'Again ??? Required ???
            mov   eax,[DDS]
            push  eax               'Uses FOUR bytes
            pop   es                'Uses FOUR bytes
            cld

            ' Make a debug copy of the main LTB buffer (after Int-31)

            mov   esi,[Debug_It]    'Debugging?
            mov   ecx,[esi+0]
            jcxz  d2                'No
            mov   esi,[LTB]         'Main LTB
            mov   edi,[LTB2_Buf]    'Our destination
            mov   ecx,512           '2048 BYTES (0-2047)
            push  ds                'Swap DS/ES
            push  es
            pop   ds
            pop   es
        rep movsd                   'Move all bytes, DS:ESI to ES:EDI
            push  ds                'Restore DS/ES
            push  es
            pop   ds
            pop   es
            mov   edi,[LTB]         'Restore EDI
        d2:

            ' Analyse the results

            mov   bl,es:[edi+0x20]  'FLAGS
            shr   bl,1              'Now we have it in flag (C)
            jc    x2                'F**K, didn't work :-(

            mov   al,es:[edi+0x1C]  'AL=0?
            or    al,al
            je    x5

            add   edi,0x50          '!!! now points to result buffer directly !!!
            xor   ecx,ecx
            mov   cx,es:[edi+0]     'Resulting buffer-size
            cmp   cx,32             'Minimum
            jb    x3                'Too small, result = :-(
            cmp   cx,63
            ja    x4                'Too big, result = :-( as well

            mov   edi,[VLN32DEST]   'Our destination
            mov   esi,[LTB]         'Original EDI, not +$50 +blah !!!
            add   esi,0x50          '& Skip DPMI simulant structure + more garbage
            push  ds                'Swap DS/ES
            push  es
            pop   ds
            pop   es
            mov   ecx,0x10          '$10 -> $40 bytes | see above about 24 top bits
        rep movsd                   '[ES:EDI]<-[DS:ESI] | ESI and EDI trashed
            push  ds                'Restore DS/ES
            push  es
            pop   ds
            pop   es

            jmp   short x9          'Done

        ' Errors...

        x0: mov   edi,[I21_Err1_Addr] 'Strange Selector [DDS} address!
            mov   word ptr [edi+0],65433
            mov   edi,[I21_Err2_Addr]  'Hi-Bits of "ES"???
            mov   eax,[DDS]
            shr   eax,16             'Get 16 Hi-bits
            mov   [edi+0],ax
            jmp   short x9

        x1: mov   esi,[I31_Err_Addr] 'Error-Code (Int-31: AX)?
            mov   [esi+0],ax
            jmp   short x9

        x2: mov   edi,[LTB]          '(assumes ES ok)
            mov   eax,es:[edi+0x1c]  'Error-Code (Int-21: AX)?
            mov   edi,[I21_Err1_Addr]
            mov   [edi+0],ax
            jmp   short x9

        x3: mov   edi,[I21_Err1_Addr] 'Buffer too small (< 32)
            mov   word ptr [edi+0],65432
            mov   edi,[I21_Err2_Addr]  'Buffer-Size
            mov   [edi+0],cx
            jmp   short x9

        x4: mov   edi,[I21_Err1_Addr] 'Buffer too big (> 63)
            mov   word ptr [edi+0],65431
            mov   edi,[I21_Err2_Addr]  'Buffer-Size
            mov   [edi+0],cx
            jmp   short x9

        x5: mov   edi,[I21_Err1_Addr] 'Int-21, AL=0
            mov   word ptr [edi+0],65430
            mov   edi,[I21_Err2_Addr]
            mov   [edi+0],ax
            jmp   short x9

        x9: pop   es                 'Restore
            pop   ds
    END ASM

END SUB

'--------------------------------------------------------------------
' Int $21/AX=$7303 GetDiskFreeSpaceEx
'
' FreeDOS, EDR-DOS, RX-DOS (Windoze95) -
' FAT32 - GET EXTENDED FREE SPACE ON DRIVE
'
' BEWARE: There is a PATH BUG !!! "C:\" - FreeDOS | "C:" - EDR-DOS
' Further FreeDOS rejects "." , while EDR-DOS accepts it.
' BUG: EDR-DOS returns with AL=0 AND CF=0 if "\" is present !!!
' You should check for success size, not for failure via AL=0 !!!
'
' AX = $7303
'   DS:DX -> ASCIZ string for drive ("C:\" or "\\SERVER\Share")
'   ES:DI -> buffer for extended free space structure (see #01789)
'   CX = length of buffer for extended free space (can be cca 36...44 bytes)
'
' Return:
'   CF clear if successful ||| ES:DI buffer filled
'   CF set on error        ||| AX = error code
'
' Notes: On DOS versions which do not support the FAT32 calls,
' this function returns CF clear/AL=0 (which is the DOS v1+
' method for reporting unimplemented functions). Under DOG 7.x
' (i.e. "MSDOG Mode" under Windoze95), the ASCIZ string pointed
' at by DS:DX *must* include the drive letter, or this function
' will return CF set/AX=$15 (invalid drive). In a DOG box,
' omitting the drive letter (DS:DX -> "\") results in the free
' space for the current default drive, as expected
'
' BUG (IRRELEVANT): This function returns a maximum of "2GB" free space
' even on an FAT32 partition larger than "2GB" under some versions of Win95
' and Win98, apparently by limiting the number of reported free clusters to
' no more than "64K" -- but only in a DOG window if a TSR has hooked INT $21
'
' See Also: AX=$7302 - AX=$7304 - AX=$7305 - AH=$36
'
' Format of extended free space structure: Offset Size Description
' (Table 01789)
'   $00 WORD  ret: size of returned structure
'   $02 WORD  call: requested struct version (0) | ret: actual struct version (0)
'   $04 DWORD number of sectors per cluster (with adjustment for compression)
'   $08 DWORD number of bytes per sector
'   $0C DWORD number of available clusters
'   $10 DWORD total number of clusters on the drive
'   $14 DWORD number of physical sectors available on the drive, without adj for compr
'   $18 DWORD total number of physical sectors on the drive, without adj for compr
'   $1C DWORD number of available allocation units, without adj for compr
'   $20 DWORD total allocation units, without adjustment for compression
'   $24 8 BYTE's reserved
'
'--------------------------------------------------------------------


I was looking for a "GetDiskFreeSpaceEx()" function, but, unfortunately (AFAIK!), the above code does not suit, and, so far, I've not found anything that will:
- Run as a 16-bit Real-mode app, in the "MSDOS" environment of Windows-XP (technically, DOS 5.00.500), which does not support the Int21-73xx functions,
- Get the ACTUAL Disk Free Space - not restricted by the 2GB limits of the Int21-36 function.
- Preferably, run quite fast (just like the above Int-21 functions).

So far, I've been thinking of: Use the standard Int21-36 Function, and, if the free space is very low, then check if the 2GB limit might apply:
- "DIR xxx.yyy" DOES see the total free space, but I expect this may be using the "windows" functions internally, which I might not be able to access in a 16-bit RM app. I could run an actual "DIR ...", and parse the output, but that might be slow and cumbersome...
- Maybe try instantly creating/deleting a "huge" temp file, larger than the Int21-36 "free" result, and, if it succeeds, then the 2GB limit has triggered! Probably not recommended to create a huge file, even for millisecs, IF there is actually little free disk space available!!

Any suggestions most welcome!

- Mike

Return to “DOS”

Who is online

Users browsing this forum: No registered users and 1 guest