• Please review our updated Terms and Rules here

Finding all valid DOS drive letters (unobtrusively)

VileR

Veteran Member
Joined
Jul 21, 2011
Messages
656
Location
Israel
I'm looking for a way to query DOS and enumerate all available drives in the system. My target is MS/PC-DOS 3.0 and above.

For floppy drives, the sample code at http://www.fysnet.net/getdrvs.htm uses an acceptable method (already discussed here in the past).

For non-floppy drives however, that same program simply tries to change the current drive (INT 21h function 0Eh) and then access it (function 19h). Another suggestion I've seen is to use function 29h (parse filename into FCB), which returns AL=FFh if the drive letter is invalid.

What I'd like to know is, what happens if the drive is removable (CD-ROM/Zip/etc.)? Will these methods cause DOS to check for available media (possibly waiting quite a long time) before telling me if the drive letter is valid?

The idea is to do this as unobtrusively as possible - at this point I only need to know if the drive *letter* is recognized, so I'd like to avoid annoying delays like that.
 
How about INT 21h function 44h (IOCTL) with AL=8 (Device Removable Query)? I think this will return CF=1 if the drive letter doesn't exist. Ultima VI (when run on DOS 3.0+) will enumerate over all 26 drive letters and call this on each one to determine which drives to look at for its disks. I used to play it on a machine with a CD-ROM drive and I don't remember it pausing on start to see if optical media was present. There is a note in HELPPC that "device drivers compatible with DOS 2.0 do not always respond correctly to this query" but that may not matter if it's just the availability of the drive letter that you care about and not the removable status.
 
Thanks, I'll have to give that a test with DOS - I wonder if it's equivalent to the function 29th (FCB) method for my purposes. But yes, I don't need to know if the drive is removable; I just need a list of valid drives handy for a file-system navigation widget. So as long as it doesn't cause DOS to make spurious accesses to the drive, it should work well enough.
 
Just be sure to test on real hardware :) I made that mistake once, I did a 'stat' on 'con' on all drives, a:\con etc. worked great in dosbox, I got a list of drives that were present or not from a-z... total fail on real hardware!
 
How about INT 21h function 44h (IOCTL) with AL=8 (Device Removable Query)? I think this will return CF=1 if the drive letter doesn't exist.

Yeah, that should work fine especially since it is supported from DOS 3.0. In XTIDECFG.COM I used INT 21h AX=4409h (CHECK_IF_BLOCK_DEVICE_REMOTE) which will also return CF set if the drive doesn't exist and it won't access the drive so there are no delays. It does require DOS 3.1 though so might not be suitable in this case.

See this file if you want to see how it's done in XTIDECFG.COM (I'm not saying this is the best way to do it - In fact, I would very much like to hear any suggestions for improvement).
 
Does this work on drive letters that are the object of subst?

Do you mean function 4408h or 4409h? In the case of 4409h then yes, at least according to RBIL. I haven't tried it myself as I don't think it really matters in this use case.
 
Just be sure to test on real hardware :) I made that mistake once, I did a 'stat' on 'con' on all drives, a:\con etc. worked great in dosbox, I got a list of drives that were present or not from a-z... total fail on real hardware!
I don't have any such removable drives to test on the real hardware (to rule out the delays), hence my asking here. :) I guess PCem, 86box or DOSBox-X might emulate them a little more accurately than DOSBox, but I still don't want to put all my trust in that.

Yeah, that should work fine especially since it is supported from DOS 3.0. In XTIDECFG.COM I used INT 21h AX=4409h (CHECK_IF_BLOCK_DEVICE_REMOTE) which will also return CF set if the drive doesn't exist and it won't access the drive so there are no delays. It does require DOS 3.1 though so might not be suitable in this case.
Ah, nice- that makes me somewhat confident that AL=08h would work similarly. A minimum requirement of 3.1 wouldn't be a huge deal actually, but I guess I'll go with the 3.0-supporting alternative.

See this file if you want to see how it's done in XTIDECFG.COM (I'm not saying this is the best way to do it - In fact, I would very much like to hear any suggestions for improvement).
Well, I've also been considering the issue with single-floppy systems and those "insert disk for drive A:/B:" DOS prompts. Your chosen solution is good enough for me (reading 0:504h to determine the current logical drive, and "hiding" the other one), but I still wonder if there's a way to go one better.

What if you intercepted the requested drive number, modified the flag in 0:504h to match it, and only then executed the DOS call? That way DOS *should* play nice with whatever letter the user selects; the advantage is that you don't have to hide a potentially-valid drive letter, or have a weird-looking list starting with B:... assuming there are no other gotchas, of course.
 
Well, I've also been considering the issue with single-floppy systems and those "insert disk for drive A:/B:" DOS prompts. Your chosen solution is good enough for me (reading 0:504h to determine the current logical drive, and "hiding" the other one), but I still wonder if there's a way to go one better.

There is actually. Almost a month after the other thread I got a PM from Malc where he had discovered this knowledge base article;
Code:
Accessing Drive w/o MS-DOS Message on Single Floppy System
Last reviewed: July 17, 1997
Article ID: Q71202 


5.10 6.00 6.00a 6.00ax 7.00 | 1.00 1.50 
MS-DOS | WINDOWS

kbprg kbcode 

The information in this article applies to: 

The C Run-time (CRT), included with: 


- Microsoft C for MS-DOS, versions 5.1, 6.0, 6.0a, and 6.0ax
- Microsoft C/C++ for MS-DOS, version 7.0
- Microsoft Visual C++ for Windows, versions 1.0 and 1.5




SUMMARY
When writing a program that accesses a floppy disk, the machine the program runs on may have only one floppy drive. In this scenario, the first access to drive B will generate the following message from MS-DOS: 


Insert Diskette for Drive B: and press any key when ready


This message is generated because MS-DOS allows a single physical floppy drive to be accessed as both the A and B logical drives. Unfortunately, the message will be written to the screen starting at the current cursor position, which may be undesirable in many cases. 


MORE INFORMATION
To avoid this message, you can first determine if only a single floppy drive is present in the system by calling Interrupt 11h. Interrupt 11h returns an equipment list code in AX, where bit zero will be 1 if there are floppy disk drives installed in the system, and bits 6 and 7 will be the number of floppy drives. If you determine that you are working on a single floppy system, then you can call Interrupt 21h, Function 44h, Subfunction 0Fh to indicate the drive you want to access next. 

Once the call to this subfunction is made, MS-DOS will assume the correct disk is in the drive and will not generate the above message. When you want to switch drives again, call the same Subfunction 0Fh with the new drive. The following sample code illustrates this procedure: 



Sample Code

/* Compile options needed:
*/

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


union REGS inregs, outregs; 

#define TRUE 1
#define FALSE 0
#define DRIVE_A 0x01
#define DRIVE_B 0x02


static int SingleFloppy = TRUE; // Assume one floppy. 

void main(void);
void SetDrive(char);

void main(void)

{ 
// Int 11h returns the equipment list code in AX.
// Bit 0 indicates whether a floppy is installed.
// Bits 6 and 7 indicate the number of drives (zero based).

int86(0x11, &inregs, &outregs);

// Do we only have one floppy drive?

if (outregs.x.ax & 0xC0)
SingleFloppy = FALSE;

// Set the initial logical status to drive A.

SetDrive(DRIVE_A);

// From this point on, MS-DOS thinks that physical drive A is the
// same as logical drive A. If you want to write to logical drive
// B, merely call SetDrive() with DRIVE_B instead.

} 

void SetDrive(char DriveNum)

{ 
if (SingleFloppy)
{
inregs.x.ax = 0x440f;
inregs.h.bl = DriveNum;
intdos(&inregs, &outregs);
}

} 




--------------------------------------------------------------------------------
Additional reference words: kbinf 6.00 6.00a 6.00ax 7.00 1.00 1.50
KBCategory: kbprg kbcode
KBSubcategory: CRTIss
Keywords : kb16bitonly


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY. 

Last reviewed: July 17, 1997
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.

I haven't tried it yet but I'm sure this is the best way to do it. It requires DOS 3.2 though which isn't mentioned in the article. Sidenote; it's a pity all the old knowledge base articles like this are gone. Does anyone know if they have been saved somewhere?

What if you intercepted the requested drive number, modified the flag in 0:504h to match it, and only then executed the DOS call? That way DOS *should* play nice with whatever letter the user selects; the advantage is that you don't have to hide a potentially-valid drive letter, or have a weird-looking list starting with B:... assuming there are no other gotchas, of course.

That might actually be the easiest way to do it. But as I said in the other thread, once you modify this byte MS-DOS 6.22 seemingly ignores it after that point and won't print anything - at least in my (very) limited testing.
 
Whoops - I missed that in the other thread's last post, thanks for the heads up.

That KB article might help explain your finding with 6.22, too. I'd imagine that around 3.2 (or later), the need arose to apply logical-drive mapping in other scenarios than single-floppy systems. The IOCTL interface got a shiny new expanded structure for that purpose; later DOS versions kept updating the vestigial flag at 0:504h for compatibility, but in case of a mismatch it was simply disregarded.

If so, maybe some judicious one-two combo (setting the Logical Drive Map for 3.2+, and poking the flag at 0:504h for older versions) could be a neat all-around solution without the need to 'hide' any drive letters. But in my case that would truly be "over-engineering", so the either-or approach is good enough for me. :)
 
Sidenote; it's a pity all the old knowledge base articles like this are gone. Does anyone know if they have been saved somewhere.
I suspect mainly on collections of MSDN / Technet / MSPL CD's now, Have you seen this site
 
Just be sure to test on real hardware :) I made that mistake once, I did a 'stat' on 'con' on all drives, a:\con etc. worked great in dosbox, I got a list of drives that were present or not from a-z... total fail on real hardware!

Device names seem to be handled differently depending on whether there is a path (including "\") after the drive letter. You can try this on the command line:

type a:nul
will not access drive, but print an error message if it doesn't exist

type a:\nul
accesses drive, "abort/retry/fail" prompt if no media

truename a:nul
truename a:\dev\nul
both print "A:/NUL" (with forward slash), error if no such drive. The path "\dev\" is still treated as a special case on MS-DOS 6.22!

truename a:\nul
accesses drive, prints "A:\NUL" if successful

Since function 29h doesn't deal with paths, and only parses the filename instead of trying to do anything with it, it should be a safe way to detect if a drive exists.
 
back in the day of heavy(!) Borland Pascal programming I used this code

Function DriveValid(Drive:Char):Boolean;Assembler;
ASM
MOV DL,Drive
MOV AH,$36
SUB DL,'A'-1
INT $21
INC AX
JE @Fine
MOV AL,1
@Fine:
END;

Don't know if it helps...
 
The method I have found to be most compatible with "modern DOS" (including NTVDM) is a combination of Int 21/AX=4409h (IOCTL - Check if block device remote) and Int 2F/AX=150Bh (MSCDEX - Drive check). This should find all drives without accessing them.
 
Appreciate the tips. I had a go at a few of these methods under DOS 6.22, on real hardware (no removable drives) and PCem (with an emulated CD-ROM):

  • Int 21h/AH=29h (DOS 1+ - parse filename into FCB): works fine.
  • Int 21h/AX=4409h (DOS 3.1+ - IOCTL - check if block device remote): works fine as well.
  • Int 21h/AX=4408h (DOS 3.0+ - IOCTL - check if block device removable): weirdly enough that one barfed on the CD-ROM drive; it returned with CF set (error) and AX=01 (which would normally mean "function number invalid").
That last one doesn't make much sense to me, but the other two seem to do the trick (without any uncalled-for media accesses), so I'm fine with either. Will probably go with the first one for slightly wider support.
 
I suspect mainly on collections of MSDN / Technet / MSPL CD's now, Have you seen this site

I had not seen that site. Thanks! There's a ton of useful info there.

The method I have found to be most compatible with "modern DOS" (including NTVDM) is a combination of Int 21/AX=4409h (IOCTL - Check if block device remote) and Int 2F/AX=150Bh (MSCDEX - Drive check). This should find all drives without accessing them.

So Int 21h/AX=4409h is not enough? When do you need to use Int 2Fh/AX=150Bh?
 
Back
Top